feat: welcome overlay + editor improvements

pull/6775/head
NGPixel 2 years ago
parent b979e50830
commit d2a18eca3c
No known key found for this signature in database
GPG Key ID: B755FB6870B30F63

@ -9,6 +9,8 @@ git config oh-my-zsh.hide-info 1
echo "Waiting for DB container to come online..."
/usr/local/bin/wait-for localhost:5432 -- echo "DB ready"
npm install -g npm-check-updates
echo "Installing dependencies..."
cd server
npm install

@ -13,5 +13,9 @@
"i18n-ally.localesPaths": [
"ux/src/i18n/locales"
],
"i18n-ally.keystyle": "flat"
"i18n-ally.keystyle": "flat",
"i18n-ally.sortKeys": true,
"i18n-ally.enabledFrameworks": [
"vue"
]
}

@ -198,7 +198,7 @@
},
"ext": "js,json,graphql,gql",
"watch": [
"./"
"server"
]
}
}

1305
ux/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -11,41 +11,42 @@
"lint": "eslint --ext .js,.vue ./"
},
"dependencies": {
"@apollo/client": "3.7.7",
"@apollo/client": "3.7.11",
"@lezer/common": "1.0.2",
"@quasar/extras": "1.15.10",
"@tiptap/core": "2.0.0-beta.212",
"@tiptap/extension-code-block": "2.0.0-beta.212",
"@tiptap/extension-code-block-lowlight": "2.0.0-beta.212",
"@tiptap/extension-color": "2.0.0-beta.212",
"@tiptap/extension-dropcursor": "2.0.0-beta.212",
"@tiptap/extension-font-family": "2.0.0-beta.212",
"@tiptap/extension-gapcursor": "2.0.0-beta.212",
"@tiptap/extension-hard-break": "2.0.0-beta.212",
"@tiptap/extension-highlight": "2.0.0-beta.212",
"@tiptap/extension-history": "2.0.0-beta.212",
"@tiptap/extension-image": "2.0.0-beta.212",
"@tiptap/extension-mention": "2.0.0-beta.212",
"@tiptap/extension-placeholder": "2.0.0-beta.212",
"@tiptap/extension-table": "2.0.0-beta.212",
"@tiptap/extension-table-cell": "2.0.0-beta.212",
"@tiptap/extension-table-header": "2.0.0-beta.212",
"@tiptap/extension-table-row": "2.0.0-beta.212",
"@tiptap/extension-task-item": "2.0.0-beta.212",
"@tiptap/extension-task-list": "2.0.0-beta.212",
"@tiptap/extension-text-align": "2.0.0-beta.212",
"@tiptap/extension-text-style": "2.0.0-beta.212",
"@tiptap/extension-typography": "2.0.0-beta.212",
"@tiptap/pm": "2.0.0-beta.212",
"@tiptap/starter-kit": "2.0.0-beta.212",
"@tiptap/vue-3": "2.0.0-beta.212",
"@mdi/font": "7.2.96",
"@quasar/extras": "1.16.1",
"@tiptap/core": "2.0.1",
"@tiptap/extension-code-block": "2.0.1",
"@tiptap/extension-code-block-lowlight": "2.0.1",
"@tiptap/extension-color": "2.0.1",
"@tiptap/extension-dropcursor": "2.0.1",
"@tiptap/extension-font-family": "2.0.1",
"@tiptap/extension-gapcursor": "2.0.1",
"@tiptap/extension-hard-break": "2.0.1",
"@tiptap/extension-highlight": "2.0.1",
"@tiptap/extension-history": "2.0.1",
"@tiptap/extension-image": "2.0.1",
"@tiptap/extension-mention": "2.0.1",
"@tiptap/extension-placeholder": "2.0.1",
"@tiptap/extension-table": "2.0.1",
"@tiptap/extension-table-cell": "2.0.1",
"@tiptap/extension-table-header": "2.0.1",
"@tiptap/extension-table-row": "2.0.1",
"@tiptap/extension-task-item": "2.0.1",
"@tiptap/extension-task-list": "2.0.1",
"@tiptap/extension-text-align": "2.0.1",
"@tiptap/extension-text-style": "2.0.1",
"@tiptap/extension-typography": "2.0.1",
"@tiptap/pm": "2.0.1",
"@tiptap/starter-kit": "2.0.1",
"@tiptap/vue-3": "2.0.1",
"apollo-upload-client": "17.0.0",
"browser-fs-access": "0.31.2",
"browser-fs-access": "0.33.0",
"clipboard": "2.0.11",
"codemirror": "5.65.11",
"codemirror-asciidoc": "1.0.4",
"dependency-graph": "0.11.0",
"filesize": "10.0.6",
"filesize": "10.0.7",
"filesize-parser": "1.5.0",
"fuse.js": "6.6.2",
"graphql": "16.6.0",
@ -54,45 +55,45 @@
"jwt-decode": "3.1.2",
"lodash-es": "4.17.21",
"lowlight": "2.8.1",
"luxon": "3.2.1",
"pinia": "2.0.30",
"prosemirror-commands": "1.5.0",
"luxon": "3.3.0",
"pinia": "2.0.33",
"prosemirror-commands": "1.5.1",
"prosemirror-history": "1.3.0",
"prosemirror-keymap": "1.2.0",
"prosemirror-keymap": "1.2.1",
"prosemirror-model": "1.19.0",
"prosemirror-schema-list": "1.2.2",
"prosemirror-state": "1.4.2",
"prosemirror-transform": "1.7.1",
"prosemirror-view": "1.30.1",
"prosemirror-view": "1.30.2",
"pug": "3.0.2",
"quasar": "2.11.5",
"slugify": "1.6.5",
"socket.io-client": "4.5.4",
"quasar": "2.11.9",
"slugify": "1.6.6",
"socket.io-client": "4.6.1",
"tabulator-tables": "5.4.4",
"tippy.js": "6.3.7",
"uuid": "9.0.0",
"v-network-graph": "0.8.1",
"v-network-graph": "0.9.1",
"vue": "3.2.47",
"vue-i18n": "9.2.2",
"vue-router": "4.1.6",
"vue3-otp-input": "0.3.6",
"vue3-otp-input": "0.4.1",
"vuedraggable": "4.1.0",
"xterm": "5.1.0",
"zxcvbn": "4.4.2"
},
"devDependencies": {
"@intlify/unplugin-vue-i18n": "0.8.1",
"@quasar/app-vite": "1.2.0",
"@types/lodash": "4.14.191",
"@volar/vue-language-plugin-pug": "1.0.24",
"@intlify/unplugin-vue-i18n": "0.10.0",
"@quasar/app-vite": "1.2.1",
"@types/lodash": "4.14.192",
"@volar/vue-language-plugin-pug": "1.2.0",
"autoprefixer": "10.4.14",
"browserlist": "latest",
"eslint": "8.33.0",
"eslint": "8.37.0",
"eslint-config-standard": "17.0.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-n": "15.6.1",
"eslint-plugin-n": "15.7.0",
"eslint-plugin-promise": "6.1.1",
"eslint-plugin-vue": "9.9.0"
"eslint-plugin-vue": "9.10.0"
},
"engines": {
"node": ">= 18.0",

@ -50,7 +50,7 @@ module.exports = configure(function (/* ctx */) {
extras: [
// 'ionicons-v4',
// 'mdi-v5',
'mdi-v7',
// 'mdi-v7',
// 'fontawesome-v6',
// 'eva-icons',
// 'themify',

@ -10,6 +10,8 @@ import { useSiteStore } from 'src/stores/site'
import { useUserStore } from 'src/stores/user'
import { setCssVar, useQuasar } from 'quasar'
import '@mdi/font/css/materialdesignicons.css'
/* global siteConfig */
// QUASAR

@ -31,19 +31,37 @@
@click='insertTable'
)
q-tooltip(anchor='center right' self='center left') {{ t('editor.markup.insertTable') }}
q-btn(
icon='mdi-tab-plus'
padding='sm sm'
flat
)
q-tooltip(anchor='center right' self='center left') {{ t('editor.markup.insertTabset') }}
q-btn(
icon='mdi-chart-multiline'
padding='sm sm'
flat
)
q-tooltip(anchor='center right' self='center left') {{ t('editor.markup.insertDiagram') }}
q-btn(
icon='mdi-book-plus'
padding='sm sm'
flat
)
q-tooltip(anchor='center right' self='center left') {{ t('editor.markup.insertFootnote') }}
q-btn(
icon='mdi-cookie-plus'
padding='sm sm'
flat
)
q-tooltip(anchor='center right' self='center left') {{ t('editor.markup.insertEmoji') }}
q-btn(
icon='mdi-line-scan'
padding='sm sm'
flat
@click='insertHorizontalBar'
)
q-tooltip(anchor='center right' self='center left') {{ t('editor.markup.horizontalBar') }}
q-tooltip(anchor='center right' self='center left') {{ t('editor.markup.insertHorizontalBar') }}
q-space
span.editor-markdown-type Markdown
.editor-markdown-mid
@ -55,18 +73,21 @@
icon='mdi-format-bold'
padding='xs sm'
flat
@click='toggleMarkup({ start: `**` })'
)
q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.bold') }}
q-btn(
icon='mdi-format-italic'
padding='xs sm'
flat
@click='toggleMarkup({ start: `*` })'
)
q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.italic') }}
q-btn(
icon='mdi-format-strikethrough'
padding='xs sm'
flat
@click='toggleMarkup({ start: `~~` })'
)
q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.strikethrough') }}
q-btn(
@ -75,16 +96,28 @@
flat
)
q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.header') }}
q-menu(auto-close)
q-list(separator)
q-item(
v-for='lvl in 6'
clickable
@click='setHeaderLine(lvl)'
)
q-item-section(side)
q-icon(:name='`mdi-format-header-` + lvl')
q-item-section {{ t('editor.markup.headerLevel', { level: lvl }) }}
q-btn(
icon='mdi-format-subscript'
padding='xs sm'
flat
@click='toggleMarkup({ start: `~` })'
)
q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.subscript') }}
q-btn(
icon='mdi-format-superscript'
padding='xs sm'
flat
@click='toggleMarkup({ start: `^` })'
)
q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.superscript') }}
q-btn(
@ -92,29 +125,71 @@
padding='xs sm'
flat
)
q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.blockquote') }}
q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.blockquoteAdmonitions') }}
q-menu(auto-close)
q-list(separator)
q-item(clickable, @click='insertBeforeEachLine({ content: `> `})')
q-item-section(side)
q-icon(name='mdi-format-quote-close')
q-item-section {{ t('editor.markup.blockquote') }}
q-item(clickable, @click='insertBeforeEachLine({ content: `> `, after: `{.is-info}`})')
q-item-section(side)
q-icon(name='mdi-information-box', color='blue-7')
q-item-section {{ t('editor.markup.admonitionInfo') }}
q-item(clickable, @click='insertBeforeEachLine({ content: `> `, after: `{.is-success}`})')
q-item-section(side)
q-icon(name='mdi-check-circle', color='positive')
q-item-section {{ t('editor.markup.admonitionSuccess') }}
q-item(clickable, @click='insertBeforeEachLine({ content: `> `, after: `{.is-warning}`})')
q-item-section(side)
q-icon(name='mdi-alert-box', color='orange')
q-item-section {{ t('editor.markup.admonitionWarning') }}
q-item(clickable, @click='insertBeforeEachLine({ content: `> `, after: `{.is-danger}`})')
q-item-section(side)
q-icon(name='mdi-close-box', color='negative')
q-item-section {{ t('editor.markup.admonitionDanger') }}
q-btn(
icon='mdi-format-list-bulleted'
padding='xs sm'
flat
@click='insertBeforeEachLine({ content: `- `})'
)
q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.unorderedList') }}
q-btn(
icon='mdi-format-list-numbered'
padding='xs sm'
flat
@click='insertBeforeEachLine({ content: `1. `})'
)
q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.orderedList') }}
q-btn(
icon='mdi-format-list-checks'
padding='xs sm'
flat
)
q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.taskList') }}
q-menu(auto-close)
q-list(separator)
q-item(clickable, @click='insertBeforeEachLine({ content: `- [ ] `})')
q-item-section(side)
q-icon(name='mdi-checkbox-blank-outline')
q-item-section {{ t('editor.markup.taskListUnchecked') }}
q-item(clickable, @click='insertBeforeEachLine({ content: `- [x] `})')
q-item-section(side)
q-icon(name='mdi-checkbox-outline')
q-item-section {{ t('editor.markup.taskListChecked') }}
q-btn(
icon='mdi-code-tags'
padding='xs sm'
flat
@click='toggleMarkup({ start: "`" })'
)
q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.inlineCode') }}
q-btn(
icon='mdi-keyboard-variant'
padding='xs sm'
flat
@click='toggleMarkup({ start: `<kbd>`, end: `</kbd>` })'
)
q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.keyboardKey') }}
q-btn(
@ -161,6 +236,7 @@
import { reactive, ref, shallowRef, nextTick, onBeforeMount, onMounted, watch } from 'vue'
import { useMeta, useQuasar, setCssVar } from 'quasar'
import { useI18n } from 'vue-i18n'
import { get, flatten, last, times, startsWith } from 'lodash-es'
import { useEditorStore } from 'src/stores/editor'
import { useSiteStore } from 'src/stores/site'
@ -206,7 +282,7 @@ const state = reactive({
content: '',
previewShown: true,
previewHTML: '',
previewScrollSync: false
previewScrollSync: true
})
// Platform detection
@ -226,6 +302,34 @@ function insertTable () {
})
}
/**
* Set current line as header
*/
function setHeaderLine (lvl) {
const curLine = cm.value.doc.getCursor('head').line
let lineContent = cm.value.doc.getLine(curLine)
const lineLength = lineContent.length
if (startsWith(lineContent, '#')) {
lineContent = lineContent.replace(/^(#+ )/, '')
}
lineContent = times(lvl, n => '#').join('') + ' ' + lineContent
cm.value.doc.replaceRange(lineContent, { line: curLine, ch: 0 }, { line: curLine, ch: lineLength })
}
/**
* Get the header lever of the current line
*/
function getHeaderLevel (cm) {
const curLine = cm.doc.getCursor('head').line
const lineContent = cm.doc.getLine(curLine)
let lvl = 0
const result = lineContent.match(/^(#+) /)
if (result) {
lvl = get(result, '[1]', '').length
}
return lvl
}
/**
* Insert content at cursor
*/
@ -243,10 +347,55 @@ function insertAfter ({ content, newLine }) {
cm.value.doc.replaceRange(newLine ? `\n${content}\n` : content, { line: curLine, ch: lineLength + 1 })
}
/**
* Insert content before current line
*/
function insertBeforeEachLine ({ content, after }) {
let lines = []
if (!cm.value.doc.somethingSelected()) {
lines.push(cm.value.doc.getCursor('head').line)
} else {
lines = flatten(cm.value.doc.listSelections().map(sl => {
const range = Math.abs(sl.anchor.line - sl.head.line) + 1
const lowestLine = (sl.anchor.line > sl.head.line) ? sl.head.line : sl.anchor.line
return times(range, l => l + lowestLine)
}))
}
lines.forEach(ln => {
let lineContent = cm.value.doc.getLine(ln)
const lineLength = lineContent.length
if (startsWith(lineContent, content)) {
lineContent = lineContent.substring(content.length)
}
cm.value.doc.replaceRange(content + lineContent, { line: ln, ch: 0 }, { line: ln, ch: lineLength })
})
if (after) {
const lastLine = last(lines)
cm.value.doc.replaceRange(`\n${after}\n`, { line: lastLine, ch: cm.value.doc.getLine(lastLine).length + 1 })
}
}
/**
* Insert an Horizontal Bar
*/
function insertHorizontalBar () {
insertAfter({ content: '---', newLine: true })
}
/**
* Toggle Markup at selection
*/
function toggleMarkup ({ start, end }) {
if (!end) { end = start }
if (!cm.value.doc.somethingSelected()) {
return $q.notify({
type: 'negative',
message: t('editor.markup.noSelectionError')
})
}
cm.value.doc.replaceSelections(cm.value.doc.getSelections().map(s => start + s + end))
}
// MOUNTED
onMounted(async () => {
@ -300,23 +449,23 @@ onMounted(async () => {
return false
},
[`${CtrlKey}-B`] (c) {
// toggleMarkup({ start: '**' })
toggleMarkup({ start: '**' })
return false
},
[`${CtrlKey}-I`] (c) {
// toggleMarkup({ start: '*' })
toggleMarkup({ start: '*' })
return false
},
[`${CtrlKey}-Alt-Right`] (c) {
// let lvl = getHeaderLevel(c)
// if (lvl >= 6) { lvl = 5 }
// setHeaderLine(lvl + 1)
let lvl = getHeaderLevel(c)
if (lvl >= 6) { lvl = 5 }
setHeaderLine(lvl + 1)
return false
},
[`${CtrlKey}-Alt-Left`] (c) {
// let lvl = getHeaderLevel(c)
// if (lvl <= 1) { lvl = 2 }
// setHeaderLine(lvl - 1)
let lvl = getHeaderLevel(c)
if (lvl <= 1) { lvl = 2 }
setHeaderLine(lvl - 1)
return false
}
}
@ -336,6 +485,7 @@ onMounted(async () => {
// this.processContent(this.$store.get('editor/content'))
nextTick(() => {
cm.value.refresh()
cm.value.focus()
})
// this.$root.$on('editorInsert', opts => {
@ -408,16 +558,19 @@ $editor-height-mobile: calc(100vh - 112px - 16px);
text-orientation: mixed;
padding-bottom: 1rem;
color: rgba(255,255,255, .4);
font-weight: 500;
}
&-preview {
flex: 1 1 50%;
background-color: $grey-2;
position: relative;
height: $editor-height;
overflow: hidden;
@at-root .theme--dark & {
background-color: $grey-9;
@at-root .body--light & {
background-color: $grey-2;
}
@at-root .body--dark & {
background-color: $dark-4;
}
// @include until($tablet) {
// display: none;
@ -434,12 +587,19 @@ $editor-height-mobile: calc(100vh - 112px - 16px);
max-width: 0;
}
&-toolbar {
background-color: $grey-3;
color: $grey-8;
height: 32px;
display: flex;
align-items: center;
padding: 0 1rem;
@at-root .body--light & {
background-color: $grey-3;
}
@at-root .body--dark & {
background-color: $dark-2;
color: $grey-6;
}
}
&-content {
height: $editor-height;

@ -26,6 +26,10 @@ const overlays = {
TableEditor: defineAsyncComponent({
loader: () => import('./TableEditorOverlay.vue'),
loadingComponent: LoadingGeneric
}),
Welcome: defineAsyncComponent({
loader: () => import('./WelcomeOverlay.vue'),
loadingComponent: LoadingGeneric
})
}

@ -0,0 +1,210 @@
<template lang="pug">
.page-actions.column.items-stretch.order-last(:class='editorStore.isActive ? `is-editor` : ``')
q-btn.q-py-md(
flat
icon='las la-pen-nib'
:color='editorStore.isActive ? `white` : `deep-orange-9`'
aria-label='Page Properties'
@click='togglePageProperties'
)
q-tooltip(anchor='center left' self='center right') Page Properties
q-btn.q-py-md(
flat
icon='las la-project-diagram'
:color='editorStore.isActive ? `white` : `deep-orange-9`'
aria-label='Page Data'
@click='togglePageData'
disable
v-if='flagsStore.experimental'
)
q-tooltip(anchor='center left' self='center right') Page Data
template(v-if='!(editorStore.isActive && editorStore.mode === `create`)')
q-separator.q-my-sm(inset)
q-btn.q-py-sm(
flat
icon='las la-ellipsis-h'
:color='editorStore.isActive ? `deep-orange-2` : `grey`'
aria-label='Page Actions'
)
q-tooltip(anchor='center left' self='center right') Page Actions
q-menu(
anchor='top left'
self='top right'
auto-close
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')
q-item-section
q-item-label Convert Page
q-item(clickable)
q-item-section.items-center(avatar)
q-icon(color='deep-orange-9', name='las la-magic', size='sm')
q-item-section
q-item-label Re-render Page
q-item(clickable)
q-item-section.items-center(avatar)
q-icon(color='deep-orange-9', name='las la-sun', size='sm')
q-item-section
q-item-label View Backlinks
q-space
template(v-if='!(editorStore.isActive && editorStore.mode === `create`)')
q-btn.q-py-sm(
flat
icon='las la-copy'
:color='editorStore.isActive ? `deep-orange-2` : `grey`'
aria-label='Duplicate Page'
@click='duplicatePage'
)
q-tooltip(anchor='center left' self='center right') Duplicate Page
q-btn.q-py-sm(
flat
icon='las la-share'
:color='editorStore.isActive ? `deep-orange-2` : `grey`'
aria-label='Rename / Move Page'
@click='renamePage'
)
q-tooltip(anchor='center left' self='center right') Rename / Move Page
q-btn.q-py-sm(
flat
icon='las la-trash'
:color='editorStore.isActive ? `deep-orange-2` : `grey`'
aria-label='Delete Page'
@click='deletePage'
:class='editorStore.isActive ? `q-pb-md` : ``'
)
q-tooltip(anchor='center left' self='center right') Delete Page
span.page-actions-mode(v-else) {{ t('common.actions.newPage') }}
</template>
<script setup>
import { useQuasar } from 'quasar'
import { computed, defineAsyncComponent, onMounted, reactive, ref, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useEditorStore } from 'src/stores/editor'
import { useFlagsStore } from 'src/stores/flags'
import { usePageStore } from 'src/stores/page'
import { useSiteStore } from 'src/stores/site'
// QUASAR
const $q = useQuasar()
// STORES
const editorStore = useEditorStore()
const flagsStore = useFlagsStore()
const pageStore = usePageStore()
const siteStore = useSiteStore()
// ROUTER
const router = useRouter()
const route = useRoute()
// I18N
const { t } = useI18n()
// METHODS
function togglePageProperties () {
siteStore.$patch({
sideDialogComponent: 'PagePropertiesDialog',
sideDialogShown: true
})
}
function togglePageData () {
siteStore.$patch({
sideDialogComponent: 'PageDataDialog',
sideDialogShown: true
})
}
function duplicatePage () {
$q.dialog({
component: defineAsyncComponent(() => import('../components/TreeBrowserDialog.vue')),
componentProps: {
mode: 'duplicatePage',
folderPath: '',
itemId: pageStore.id,
itemTitle: pageStore.title,
itemFileName: pageStore.path
}
}).onOk(() => {
// TODO: change route to new location
})
}
function renamePage () {
$q.dialog({
component: defineAsyncComponent(() => import('../components/TreeBrowserDialog.vue')),
componentProps: {
mode: 'renamePage',
folderPath: '',
itemId: pageStore.id,
itemTitle: pageStore.title,
itemFileName: 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('/')
})
}
</script>
<style lang="scss">
.page-actions {
flex: 0 0 56px;
@at-root .body--light & {
background-color: $grey-3;
}
@at-root .body--dark & {
background-color: $dark-4;
}
&.is-editor {
@at-root .body--light & {
background-color: $deep-orange-9;
}
@at-root .body--dark & {
background-color: $deep-orange-9;
}
}
&-mode {
writing-mode: vertical-rl;
text-orientation: mixed;
padding: 1.75rem 1rem 1.75rem 0;
color: $deep-orange-3;
font-weight: 500;
}
}
</style>

@ -0,0 +1,128 @@
<template lang="pug">
q-dialog(
v-model='siteStore.sideDialogShown'
position='right'
full-height
transition-show='jump-left'
transition-hide='jump-right'
class='floating-sidepanel'
no-shake
)
component(:is='sideDialogs[siteStore.sideDialogComponent]')
</template>
<script setup>
import { useQuasar } from 'quasar'
import { computed, defineAsyncComponent, onMounted, reactive, ref, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useEditorStore } from 'src/stores/editor'
import { useFlagsStore } from 'src/stores/flags'
import { usePageStore } from 'src/stores/page'
import { useSiteStore } from 'src/stores/site'
// COMPONENTS
import LoadingGeneric from 'src/components/LoadingGeneric.vue'
const sideDialogs = {
PageDataDialog: defineAsyncComponent({
loader: () => import('src/components/PageDataDialog.vue'),
loadingComponent: LoadingGeneric
}),
PagePropertiesDialog: defineAsyncComponent({
loader: () => import('src/components/PagePropertiesDialog.vue'),
loadingComponent: LoadingGeneric
})
}
// QUASAR
const $q = useQuasar()
// STORES
const editorStore = useEditorStore()
const flagsStore = useFlagsStore()
const pageStore = usePageStore()
const siteStore = useSiteStore()
// ROUTER
const router = useRouter()
const route = useRoute()
// I18N
const { t } = useI18n()
// DATA
const state = reactive({
showSideDialog: false,
sideDialogComponent: null,
showGlobalDialog: false,
globalDialogComponent: null,
showTagsEditBtn: false,
tagEditMode: false,
tocExpanded: ['h1-0', 'h1-1'],
tocSelected: [],
currentRating: 3
})
</script>
<style lang="scss">
.floating-sidepanel {
.q-dialog__inner {
right: 24px;
.q-card {
border-radius: 4px !important;
min-width: 450px;
.q-card__section {
border-radius: 0;
}
}
}
.alt-card {
@at-root .body--light & {
background-color: $grey-2;
border-top: 1px solid $grey-4;
box-shadow: inset 0 1px 0 0 #FFF, inset 0 -1px 0 0 #FFF;
border-bottom: 1px solid $grey-4;
}
@at-root .body--dark & {
background-color: $dark-4;
border-top: 1px solid lighten($dark-3, 8%);
box-shadow: inset 0 1px 0 0 $dark-6, inset 0 -1px 0 0 $dark-6;
border-bottom: 1px solid lighten($dark-3, 8%);
}
}
&-quickaccess {
width: 40px;
border-radius: 4px !important;
background-color: rgba(0,0,0,.75);
backdrop-filter: blur(5px);
color: #FFF;
position: fixed;
right: 486px;
top: 74px;
z-index: -1;
display: flex;
flex-direction: column;
box-shadow: 0 0 5px 0 rgba(0,0,0,.5) !important;
@at-root .q-transition--jump-left-enter-active & {
display: none !important;
}
@at-root .q-transition--jump-right-leave-active & {
display: none !important;
}
}
}
</style>

@ -0,0 +1,173 @@
<template lang='pug'>
.welcome
.welcome-bg
.welcome-content
.welcome-logo
img(src='/_assets/logo-wikijs.svg')
.welcome-title {{t('welcome.title')}}
.welcome-subtitle {{t('welcome.subtitle')}}
.welcome-actions
q-btn(
push
color='primary'
:label='t(`welcome.createHome`)'
icon='las la-plus'
no-caps
)
q-menu.translucent-menu(
auto-close
anchor='top left'
self='bottom left'
)
q-list(padding)
q-item(
clickable
@click='createHomePage(`wysiwyg`)'
v-if='siteStore.editors.wysiwyg'
)
blueprint-icon(icon='google-presentation')
q-item-section.q-pr-sm Using the Visual Editor
q-item-section(side): q-icon(name='mdi-chevron-right')
q-item(
clickable
@click='createHomePage(`markdown`)'
v-if='siteStore.editors.markdown'
)
blueprint-icon(icon='markdown')
q-item-section.q-pr-sm Using the Markdown Editor
q-item-section(side): q-icon(name='mdi-chevron-right')
q-item(
clickable
@click='createHomePage(`asciidoc`)'
v-if='siteStore.editors.asciidoc'
)
blueprint-icon(icon='asciidoc')
q-item-section.q-pr-sm Using the AsciiDoc Editor
q-item-section(side): q-icon(name='mdi-chevron-right')
q-btn(
push
color='primary'
:label='t(`welcome.admin`)'
icon='las la-cog'
no-caps
@click='loadAdmin'
)
</template>
<script setup>
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useMeta, useQuasar } from 'quasar'
import { useSiteStore } from 'src/stores/site'
import { usePageStore } from 'src/stores/page'
// QUASAR
const $q = useQuasar()
// STORES
const pageStore = usePageStore()
const siteStore = useSiteStore()
// ROUTER
const router = useRouter()
// I18N
const { t } = useI18n()
// META
useMeta({
title: t('welcome.title')
})
// METHODS
function createHomePage (editor) {
siteStore.overlay = ''
pageStore.pageCreate({ editor, locale: 'en', path: '' })
}
function loadAdmin () {
siteStore.overlay = ''
router.push('/_admin')
}
</script>
<style lang="scss">
.welcome {
background: #FFF radial-gradient(ellipse, #FFF, #DDD);
color: $grey-9;
height: 100vh;
border: 10px solid #EEE;
border-radius: 25px !important;
&-bg {
position: absolute;
top: 50%;
left: 50%;
width: 320px;
height: 320px;
background: linear-gradient(0, #FFF 50%, $blue-5 50%);
border-radius: 50%;
filter: blur(100px);
transform: translate(-50%, -55%);
}
&-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 90vw;
}
&-logo {
user-select: none;
> img {
height: 200px;
user-select: none;
}
}
&-title {
font-size: 4rem;
font-weight: 500;
line-height: 4rem;
text-align: center;
@media (max-width: $breakpoint-md-max) {
font-size: 2.5rem;
line-height: 2.5rem;
}
}
&-subtitle {
font-size: 1.2rem;
font-weight: 500;
color: $blue-7;
line-height: 1.2rem;
margin-top: 1rem;
}
&-actions {
margin-top: 2rem;
text-align: center;
> .q-btn {
margin: 0 5px 5px 5px;
}
}
}
</style>

@ -202,6 +202,7 @@ body::-webkit-scrollbar-thumb {
@at-root .body--dark & {
background-color: rgba($dark,.7);
}
backdrop-filter: blur(10px);
> .q-card {

@ -55,9 +55,9 @@
"admin.api.revoke": "Revoke",
"admin.api.revokeConfirm": "Revoke API Key?",
"admin.api.revokeConfirmText": "Are you sure you want to revoke key {name}? This action cannot be undone!",
"admin.api.revokeSuccess": "The key has been revoked successfully.",
"admin.api.revoked": "Revoked",
"admin.api.revokedHint": "This key has been revoked and can no longer be used.",
"admin.api.revokeSuccess": "The key has been revoked successfully.",
"admin.api.subtitle": "Manage keys to access the API",
"admin.api.title": "API Access",
"admin.api.toggleStateDisabledSuccess": "API has been disabled successfully.",
@ -138,21 +138,51 @@
"admin.editors.channelDescription": "Create discussion channels to collaborate in real-time with your team.",
"admin.editors.channelName": "Discussion Channels",
"admin.editors.configuration": "Configuration",
"admin.editors.markdown.allowHTML": "Allow HTML",
"admin.editors.markdown.allowHTMLHint": "Allow HTML tags in content.",
"admin.editors.markdown.general": "General",
"admin.editors.markdown.kroki": "Kroki",
"admin.editors.markdown.krokiHint": "Enable Kroki Diagrams Parser",
"admin.editors.markdown.krokiServerUrl": "Kroki Server URL",
"admin.editors.markdown.krokiServerUrlHint": "URL to the Kroki server used for image generation.",
"admin.editors.markdown.latexEngine": "LaTeX Engine",
"admin.editors.markdown.latexEngineHint": "Which engine to use to process TeX/LaTeX expressions.",
"admin.editors.markdown.lineBreaks": "Auto Line Breaks",
"admin.editors.markdown.lineBreaksHint": "Automatically add linebreaks within paragraphs.",
"admin.editors.markdown.linkify": "Auto-linking",
"admin.editors.markdown.linkifyHint": "Automatically convert URLs into clickable links.",
"admin.editors.markdown.multimdTable": "MultiMarkdown Table",
"admin.editors.markdown.multimdTableHint": "Enable support for MultiMarkdown Table features.",
"admin.editors.markdown.plantuml": "PlantUML",
"admin.editors.markdown.plantumlHint": "Enable PlantUML Parser",
"admin.editors.markdown.plantumlServerUrl": "PlantUML Server URL",
"admin.editors.markdown.plantumlServerUrlHint": "URL to the PlantUML server used for image generation.",
"admin.editors.markdown.quotes": "Quotes Style",
"admin.editors.markdown.quotesHint": "When typographer is enabled. Double + single quotes replacement pairs. e.g. «»„“ for Russian, „“‚‘ for German, etc.",
"admin.editors.markdown.saveSuccess": "Markdown editor configuration saved successfully.",
"admin.editors.markdown.tabWidth": "Code Block Tab Width",
"admin.editors.markdown.tabWidthHint": "Amount of spaces for each tab in code blocks.",
"admin.editors.markdown.typographer": "Typographer",
"admin.editors.markdown.typographerHint": "Enable some language-neutral replacement + quotes beautification.",
"admin.editors.markdown.underline": "Underline Emphasis",
"admin.editors.markdown.underlineHint": "Enable text underlining by using _underline_ syntax.",
"admin.editors.markdownDescription": "Use the Markdown syntax to write content. Includes real-time preview and code completion features.",
"admin.editors.markdownName": "Markdown Editor",
"admin.editors.redirectDescription": "Create redirections to other pages / external links.",
"admin.editors.redirectName": "Redirection",
"admin.editors.saveSuccess": "Editors state saved successfully.",
"admin.editors.subtitle": "Manage editors and their configuration",
"admin.editors.title": "Editors",
"admin.editors.useRenderingPipeline": "Uses the rendering pipeline.",
"admin.editors.wysiwygDescription": "A visual WYSIWYG editor. The recommended editor for non-technical users.",
"admin.editors.wysiwygName": "Visual Editor",
"admin.extensions.incompatible": "not compatible",
"admin.extensions.install": "Install",
"admin.extensions.installed": "Installed",
"admin.extensions.installFailed": "Failed to install extension.",
"admin.extensions.installSuccess": "Extension installed successfully.",
"admin.extensions.installed": "Installed",
"admin.extensions.installing": "Installing extension...",
"admin.extensions.installingHint": "This may take a while depending on your server.",
"admin.extensions.installSuccess": "Extension installed successfully.",
"admin.extensions.instructions": "Instructions",
"admin.extensions.instructionsHint": "Must be installed manually",
"admin.extensions.reinstall": "Reinstall",
@ -188,7 +218,6 @@
"admin.general.contentLicenseHint": "License shown in the footer of all content pages.",
"admin.general.defaultDateFormat": "Default Date Format",
"admin.general.defaultDateFormatHint": "The default date format for new users.",
"admin.general.defaults": "Site Defaults",
"admin.general.defaultTimeFormat": "Default Time Format",
"admin.general.defaultTimeFormat12h": "12 hour",
"admin.general.defaultTimeFormat24h": "24 hour",
@ -197,6 +226,7 @@
"admin.general.defaultTimezoneHint": "The default timezone for new users.",
"admin.general.defaultTocDepth": "Default ToC Depth",
"admin.general.defaultTocDepthHint": "The default minimum and maximum header levels to show in the table of contents.",
"admin.general.defaults": "Site Defaults",
"admin.general.displaySiteTitle": "Display Site Title",
"admin.general.displaySiteTitleHint": "Should the site title be displayed next to the logo? If your logo isn't square and contain your brand name, turn this option off.",
"admin.general.favicon": "Favicon",
@ -235,11 +265,11 @@
"admin.general.siteHostnameHint": "Hostname this site should respond to. Set * for catch-all / fallback domain.",
"admin.general.siteHostnameInvalid": "Invalid Hostname",
"admin.general.siteInfo": "Site Info",
"admin.general.sitemap": "Allow Sitemap",
"admin.general.sitemapHint": "Make a sitemap.xml available to search engines with all pages accessible to guests.",
"admin.general.siteTitle": "Site Title",
"admin.general.siteTitleHint": "Displayed in the top bar and appended to all pages meta title.",
"admin.general.siteTitleInvalidChars": "Site Title contains invalid characters.",
"admin.general.sitemap": "Allow Sitemap",
"admin.general.sitemapHint": "Make a sitemap.xml available to search engines with all pages accessible to guests.",
"admin.general.subtitle": "Main settings of your wiki",
"admin.general.title": "General",
"admin.general.uploadClear": "Clear",
@ -251,9 +281,9 @@
"admin.general.uploadLogo": "Upload Logo",
"admin.general.uploadNormalizeFilename": "Normalize Filenames",
"admin.general.uploadNormalizeFilenameHint": "Automatically transform filenames to a standard URL-friendly format.",
"admin.general.uploads": "Uploads",
"admin.general.uploadSizeHint": "An image of {size} pixels is recommended for best results.",
"admin.general.uploadTypesHint": "{typeList} or {lastType} files only",
"admin.general.uploads": "Uploads",
"admin.general.urlHandling": "URL Handling",
"admin.groups.assignUser": "Assign User",
"admin.groups.authBehaviors": "Authentication Behaviors",
@ -297,10 +327,10 @@
"admin.groups.ruleMatchStart": "Path Starts With...",
"admin.groups.ruleMatchTag": "Has Any Tag...",
"admin.groups.ruleMatchTagAll": "Has All Tags...",
"admin.groups.rules": "Rules",
"admin.groups.ruleSites": "Site(s)",
"admin.groups.rulesNone": "This group doesn't have any rules yet.",
"admin.groups.ruleUntitled": "Untitled Rule",
"admin.groups.rules": "Rules",
"admin.groups.rulesNone": "This group doesn't have any rules yet.",
"admin.groups.selectedLocales": "Any Locale | {locale} locale only | {count} locales selected",
"admin.groups.selectedSites": "Any Site | 1 site selected | {count} sites selected",
"admin.groups.subtitle": "Manage user groups and permissions",
@ -378,10 +408,10 @@
"admin.mail.dkimUse": "Use DKIM",
"admin.mail.dkimUseHint": "Should DKIM be used when sending emails.",
"admin.mail.saveSuccess": "Configuration saved successfully.",
"admin.mail.sendTestSuccess": "A test email was sent successfully.",
"admin.mail.sender": "Sender",
"admin.mail.senderEmail": "Sender Email",
"admin.mail.senderName": "Sender Name",
"admin.mail.sendTestSuccess": "A test email was sent successfully.",
"admin.mail.smtp": "SMTP Settings",
"admin.mail.smtpHost": "Host",
"admin.mail.smtpHostHint": "Hostname or IP address of the SMTP server.",
@ -399,8 +429,8 @@
"admin.mail.smtpVerifySSLHint": "Some hosts requires SSL certificate checking to be disabled. Leave enabled for proper security.",
"admin.mail.subtitle": "Configure mail settings",
"admin.mail.templateResetPwd": "Password Reset Email",
"admin.mail.templates": "Mail Templates",
"admin.mail.templateWelcome": "Welcome Email",
"admin.mail.templates": "Mail Templates",
"admin.mail.test": "Send a test email",
"admin.mail.testHint": "Send a test email to ensure your SMTP configuration is working.",
"admin.mail.testRecipient": "Recipient Email Address",
@ -597,10 +627,10 @@
"admin.storage.assetDirectAccess": "Direct Access",
"admin.storage.assetDirectAccessHint": "Assets are accessed directly by the user using a secure / signed link. When enabled, takes priority over file streaming.",
"admin.storage.assetDirectAccessNotSupported": "Not supported by this storage target.",
"admin.storage.assetsOnly": "Assets Only",
"admin.storage.assetStreaming": "File Streaming",
"admin.storage.assetStreamingHint": "Assets will be streamed from the storage target, through the server, to the user.",
"admin.storage.assetStreamingNotSupported": "Not supported by this storage target.",
"admin.storage.assetsOnly": "Assets Only",
"admin.storage.cancelSetup": "Cancel",
"admin.storage.config": "Configuration",
"admin.storage.contentTypeDocuments": "Documents",
@ -678,21 +708,21 @@
"admin.storage.sync": "Synchronization",
"admin.storage.syncDirBi": "Bi-directional",
"admin.storage.syncDirBiHint": "In bi-directional mode, content is first pulled from the storage target. Any newer content overwrites local content. New content since last sync is then pushed to the storage target, overwriting any content on target if present.",
"admin.storage.syncDirection": "Sync Direction",
"admin.storage.syncDirectionSubtitle": "Choose how content synchronization is handled for this storage target.",
"admin.storage.syncDirPull": "Pull from target",
"admin.storage.syncDirPullHint": "Content is always pulled from the storage target, overwriting any local content which already exists. This choice is usually reserved for single-use content import. Caution with this option as any local content will always be overwritten!",
"admin.storage.syncDirPush": "Push to target",
"admin.storage.syncDirPushHint": "Content is always pushed to the storage target, overwriting any existing content. This is safest choice for backup scenarios.",
"admin.storage.syncDirection": "Sync Direction",
"admin.storage.syncDirectionSubtitle": "Choose how content synchronization is handled for this storage target.",
"admin.storage.syncSchedule": "Sync Schedule",
"admin.storage.syncScheduleCurrent": "Currently set to every {schedule}.",
"admin.storage.syncScheduleDefault": "The default is every {schedule}.",
"admin.storage.syncScheduleHint": "For performance reasons, this storage target synchronize changes on an interval-based schedule, instead of on every change. Define at which interval should the synchronization occur.",
"admin.storage.targetConfig": "Target Configuration",
"admin.storage.targets": "Targets",
"admin.storage.targetState": "This storage target is {state}",
"admin.storage.targetStateActive": "active",
"admin.storage.targetStateInactive": "inactive",
"admin.storage.targets": "Targets",
"admin.storage.title": "Storage",
"admin.storage.uninstall": "Uninstall",
"admin.storage.unsupported": "Unsupported",
@ -707,8 +737,8 @@
"admin.system.browser": "Browser",
"admin.system.browserHint": "The browser name and version.",
"admin.system.checkForUpdates": "Check",
"admin.system.checkingForUpdates": "Checking for Updates...",
"admin.system.checkUpdate": "Check / Upgrade",
"admin.system.checkingForUpdates": "Checking for Updates...",
"admin.system.client": "Client",
"admin.system.clientCookies": "Cookies Support",
"admin.system.clientCookiesHint": "Whether cookies are enabled on this browser.",
@ -768,8 +798,8 @@
"admin.terminal.clear": "Clear",
"admin.terminal.command": "Command",
"admin.terminal.connect": "Connect",
"admin.terminal.connected": "Connected.",
"admin.terminal.connectError": "Connection Error:",
"admin.terminal.connected": "Connected.",
"admin.terminal.connecting": "Connecting to server...",
"admin.terminal.disconnect": "Disconnect",
"admin.terminal.disconnected": "Disconnected.",
@ -799,10 +829,10 @@
"admin.theme.downloadName": "Name",
"admin.theme.downloadThemes": "Download Themes",
"admin.theme.fonts": "Fonts",
"admin.theme.headerColor": "Header Color",
"admin.theme.headerColorHint": "The background color for the site top header. Does not apply to the administration area.",
"admin.theme.headHtmlInjection": "Head HTML Injection",
"admin.theme.headHtmlInjectionHint": "HTML code to be injected just before the closing head tag. Usually for script tags.",
"admin.theme.headerColor": "Header Color",
"admin.theme.headerColorHint": "The background color for the site top header. Does not apply to the administration area.",
"admin.theme.iconset": "Icon Set",
"admin.theme.iconsetHint": "Set of icons to use for the sidebar navigation.",
"admin.theme.layout": "Layout",
@ -834,9 +864,9 @@
"admin.users.appearance": "Site Appearance",
"admin.users.assignGroup": "Assign Group",
"admin.users.auth": "Authentication",
"admin.users.authentication": "Authentication",
"admin.users.authProvider": "Provider",
"admin.users.authProviderId": "Provider Id",
"admin.users.authentication": "Authentication",
"admin.users.ban": "Ban User",
"admin.users.banHint": "Block the user from signing in and invalidate any active sessions.",
"admin.users.banned": "Banned",
@ -844,10 +874,10 @@
"admin.users.changePassword": "Change Password",
"admin.users.changePasswordHint": "Change the user password. Note that the current password cannot be recovered.",
"admin.users.create": "Create User",
"admin.users.createdAt": "Created {date}",
"admin.users.createInvalidData": "Cannot create user as some fields are invalid or missing.",
"admin.users.createKeepOpened": "Keep dialog opened after create",
"admin.users.createSuccess": "User created successfully!",
"admin.users.createdAt": "Created {date}",
"admin.users.darkMode": "Dark Mode",
"admin.users.darkModeHint": "Display the user interface using dark mode.",
"admin.users.dateFormat": "Date Format",
@ -868,8 +898,8 @@
"admin.users.groupAlreadyAssigned": "User is already assigned to this group.",
"admin.users.groupAssign": "Assign",
"admin.users.groupAssignNotice": "Note that you cannot assign users to the Administrators or Guests groups from this panel.",
"admin.users.groups": "Groups",
"admin.users.groupSelected": "Assign to {group}",
"admin.users.groups": "Groups",
"admin.users.groupsMissing": "You must assign the user to at least 1 group.",
"admin.users.groupsSelected": "Assign to {count} groups",
"admin.users.id": "ID",
@ -1015,13 +1045,13 @@
"admin.webhooks.eventNewComment": "Post a new comment",
"admin.webhooks.eventRenameAsset": "Rename / move an asset",
"admin.webhooks.eventRenamePage": "Rename / move a page",
"admin.webhooks.events": "Events",
"admin.webhooks.eventsMissing": "You must select at least 1 event.",
"admin.webhooks.eventsSelected": "No event selected | 1 event selected | {count} events selected",
"admin.webhooks.eventUploadAsset": "Upload a new asset",
"admin.webhooks.eventUserJoin": "Create / register a new user",
"admin.webhooks.eventUserLogin": "User logins",
"admin.webhooks.eventUserLogout": "User logouts",
"admin.webhooks.events": "Events",
"admin.webhooks.eventsMissing": "You must select at least 1 event.",
"admin.webhooks.eventsSelected": "No event selected | 1 event selected | {count} events selected",
"admin.webhooks.includeContent": "Include Content",
"admin.webhooks.includeContentHint": "Should the payload include content (e.g. the full page body). Make sure that your remote endpoint can accept large payloads!",
"admin.webhooks.includeMetadata": "Include Metadata",
@ -1072,8 +1102,8 @@
"auth.errors.missingUsername": "Username is missing.",
"auth.errors.missingVerifyPassword": "Password Verification is missing.",
"auth.errors.notYetAuthorized": "You have not been authorized to login to this site yet.",
"auth.errors.passwordsNotMatch": "Passwords do not match.",
"auth.errors.passwordTooShort": "Password is too short.",
"auth.errors.passwordsNotMatch": "Passwords do not match.",
"auth.errors.register": "One or more fields are invalid.",
"auth.errors.tfaMissing": "Missing or incomplete security code.",
"auth.errors.tooManyAttempts": "Too many attempts!",
@ -1109,10 +1139,10 @@
"auth.passwordTooShort": "Password is too short.",
"auth.pleaseWait": "Please wait",
"auth.registerCheckEmail": "Check your emails to activate your account.",
"auth.registering": "Creating account...",
"auth.registerSubTitle": "Fill-in the form below to create an account.",
"auth.registerSuccess": "Account created successfully!",
"auth.registerTitle": "Create an account",
"auth.registering": "Creating account...",
"auth.selectAuthProvider": "Sign in with",
"auth.sendResetPassword": "Reset Password",
"auth.signingIn": "Signing In...",
@ -1153,6 +1183,7 @@
"common.actions.moveTo": "Move To",
"common.actions.new": "New",
"common.actions.newFolder": "New Folder",
"common.actions.newPage": "New Page",
"common.actions.ok": "OK",
"common.actions.optimize": "Optimize",
"common.actions.page": "Page",
@ -1188,8 +1219,8 @@
"common.comments.newPlaceholder": "Write a new comment...",
"common.comments.none": "No comments yet.",
"common.comments.postComment": "Post Comment",
"common.comments.postingAs": "Posting as {name}",
"common.comments.postSuccess": "New comment posted successfully.",
"common.comments.postingAs": "Posting as {name}",
"common.comments.sdTitle": "Talk",
"common.comments.title": "Comments",
"common.comments.updateComment": "Update Comment",
@ -1336,8 +1367,8 @@
"editor.assets.folderNameNamingRulesLink": "naming rules",
"editor.assets.headerActions": "Actions",
"editor.assets.headerAdded": "Added",
"editor.assets.headerFilename": "Filename",
"editor.assets.headerFileSize": "File Size",
"editor.assets.headerFilename": "Filename",
"editor.assets.headerId": "ID",
"editor.assets.headerType": "Type",
"editor.assets.imageAlign": "Image Alignment",
@ -1375,22 +1406,33 @@
"editor.conflict.whatToDo": "What do you want to do?",
"editor.conflict.whatToDoLocal": "Use your current local version and ignore the latest changes.",
"editor.conflict.whatToDoRemote": "Use the remote version (latest) and discard your changes.",
"editor.markup.admonitionDanger": "Danger / Important Admonition",
"editor.markup.admonitionInfo": "Info / Note Admonition",
"editor.markup.admonitionSuccess": "Tip / Success Admonition",
"editor.markup.admonitionWarning": "Warning Admonition",
"editor.markup.blockquote": "Blockquote",
"editor.markup.blockquoteAdmonitions": "Blockquote / Admonition",
"editor.markup.blockquoteError": "Error Blockquote",
"editor.markup.blockquoteInfo": "Info Blockquote",
"editor.markup.blockquoteSuccess": "Success Blockquote",
"editor.markup.blockquoteWarning": "Warning Blockquote",
"editor.markup.bold": "Bold",
"editor.markup.distractionFreeMode": "Distraction Free Mode",
"editor.markup.header": "Header",
"editor.markup.headerLevel": "Header {level}",
"editor.markup.heading": "Heading {level}",
"editor.markup.horizontalBar": "Horizontal Bar",
"editor.markup.inlineCode": "Inline Code",
"editor.markup.insertAssets": "Insert Assets",
"editor.markup.insertBlock": "Insert Block",
"editor.markup.insertCodeBlock": "Insert Code Block",
"editor.markup.insertDiagram": "Insert Diagram",
"editor.markup.insertEmoji": "Insert Emoji",
"editor.markup.insertFootnote": "Insert Footnote",
"editor.markup.insertHorizontalBar": "Insert Horizontal Bar",
"editor.markup.insertLink": "Insert Link",
"editor.markup.insertMathExpression": "Insert Math Expression",
"editor.markup.insertTable": "Insert Table",
"editor.markup.insertTabset": "Insert Tabset",
"editor.markup.insertVideoAudio": "Insert Video / Audio",
"editor.markup.italic": "Italic",
"editor.markup.keyboardKey": "Keyboard Key",
@ -1401,6 +1443,9 @@
"editor.markup.subscript": "Subscript",
"editor.markup.superscript": "Superscript",
"editor.markup.tableHelper": "Table Helper",
"editor.markup.taskList": "Task List",
"editor.markup.taskListChecked": "Checked List Item",
"editor.markup.taskListUnchecked": "Unchecked List Item",
"editor.markup.togglePreviewPane": "Hide / Show Preview Pane",
"editor.markup.toggleSpellcheck": "Toggle Spellcheck",
"editor.markup.unorderedList": "Unordered List",
@ -1464,9 +1509,9 @@
"editor.props.pageProperties": "Page Properties",
"editor.props.password": "Password",
"editor.props.passwordHint": "The page must be published and the user must have read access rights.",
"editor.props.publishState": "Publishing State",
"editor.props.published": "Published",
"editor.props.publishedHint": "Visible to all users with read access.",
"editor.props.publishState": "Publishing State",
"editor.props.relationAdd": "Add Relation...",
"editor.props.relationAddHint": "Add links to other pages in the footer (e.g. as part of a series of articles)",
"editor.props.relations": "Relations",
@ -1486,6 +1531,7 @@
"editor.props.title": "Title",
"editor.props.tocMinMaxDepth": "Min/Max Depth",
"editor.props.visibility": "Visibility",
"editor.renderPreview": "Render Preview",
"editor.save.createSuccess": "Page created successfully.",
"editor.save.error": "An error occurred while creating the page",
"editor.save.pleaseWait": "Please wait...",
@ -1495,13 +1541,16 @@
"editor.select.cannotChange": "This cannot be changed once the page is created.",
"editor.select.customView": "or create a custom view?",
"editor.select.title": "Which editor do you want to use for this page?",
"editor.tableEditor.title": "Table Editor",
"editor.togglePreviewPane": "Toggle Preview Pane",
"editor.toggleScrollSync": "Toggle Scroll Sync",
"editor.unsaved.body": "You have unsaved changes. Are you sure you want to leave the editor and discard any modifications you made since the last save?",
"editor.unsaved.title": "Discard Unsaved Changes?",
"editor.unsavedWarning": "You have unsaved edits. Are you sure you want to leave the editor?",
"fileman.7zFileType": "7zip Archive",
"fileman.aacFileType": "AAC Audio File",
"fileman.aifFileType": "AIF Audio File",
"fileman.aiFileType": "Adobe Illustrator Document",
"fileman.aifFileType": "AIF Audio File",
"fileman.apkFileType": "Android Package",
"fileman.aviFileType": "AVI Video File",
"fileman.binFileType": "Binary File",
@ -1594,6 +1643,7 @@
"pageSaveDialog.displayModePath": "Browse Using Paths",
"pageSaveDialog.displayModeTitle": "Browse Using Titles",
"pageSaveDialog.title": "Save As...",
"profile.accessibility": "Accessibility",
"profile.activity": "Activity",
"profile.appearance": "Site Appearance",
"profile.appearanceDark": "Dark",
@ -1613,6 +1663,12 @@
"profile.avatarUploadHint": "For best results, use a 180x180 image of type JPG or PNG.",
"profile.avatarUploadSuccess": "Profile picture uploaded successfully.",
"profile.avatarUploadTitle": "Upload your user profile picture.",
"profile.cvd": "Color Vision Deficiency",
"profile.cvdDeuteranopia": "Deuteranopia",
"profile.cvdHint": "Alter the color scheme of certain UI elements to account for certain color vision dificiencies.",
"profile.cvdNone": "None",
"profile.cvdProtanopia": "Protanopia",
"profile.cvdTritanopia": "Tritanopia",
"profile.darkMode": "Dark Mode",
"profile.darkModeHint": "Change the appareance of the site to a dark theme.",
"profile.dateFormat": "Date Format",
@ -1664,8 +1720,8 @@
"tags.noResults": "Couldn't find any page with the selected tags.",
"tags.noResultsWithFilter": "Couldn't find any page matching the current filtering options.",
"tags.orderBy": "Order By",
"tags.orderByField.creationDate": "Creation Date",
"tags.orderByField.ID": "ID",
"tags.orderByField.creationDate": "Creation Date",
"tags.orderByField.lastModified": "Last Modified",
"tags.orderByField.path": "Path",
"tags.orderByField.title": "Title",
@ -1677,48 +1733,5 @@
"welcome.admin": "Administration Area",
"welcome.createHome": "Create the homepage",
"welcome.subtitle": "Let's get started...",
"welcome.title": "Welcome to Wiki.js!",
"admin.editors.useRenderingPipeline": "Uses the rendering pipeline.",
"admin.editors.markdown.allowHTML": "Allow HTML",
"admin.editors.markdown.allowHTMLHint": "Allow HTML tags in content.",
"admin.editors.markdown.linkify": "Auto-linking",
"admin.editors.markdown.linkifyHint": "Automatically convert URLs into clickable links.",
"admin.editors.markdown.lineBreaks": "Auto Line Breaks",
"admin.editors.markdown.lineBreaksHint": "Automatically add linebreaks within paragraphs.",
"admin.editors.markdown.typographer": "Typographer",
"admin.editors.markdown.typographerHint": "Enable some language-neutral replacement + quotes beautification.",
"admin.editors.markdown.underline": "Underline Emphasis",
"admin.editors.markdown.underlineHint": "Enable text underlining by using _underline_ syntax.",
"admin.editors.markdown.tabWidth": "Code Block Tab Width",
"admin.editors.markdown.tabWidthHint": "Amount of spaces for each tab in code blocks.",
"admin.editors.markdown.latexEngine": "LaTeX Engine",
"admin.editors.markdown.latexEngineHint": "Which engine to use to process TeX/LaTeX expressions.",
"admin.editors.markdown.general": "General",
"admin.editors.markdown.plantuml": "PlantUML",
"admin.editors.markdown.kroki": "Kroki",
"admin.editors.markdown.multimdTable": "MultiMarkdown Table",
"admin.editors.markdown.multimdTableHint": "Enable support for MultiMarkdown Table features.",
"admin.editors.markdown.plantumlHint": "Enable PlantUML Parser",
"admin.editors.markdown.krokiHint": "Enable Kroki Diagrams Parser",
"admin.editors.markdown.krokiServerUrl": "Kroki Server URL",
"admin.editors.markdown.krokiServerUrlHint": "URL to the Kroki server used for image generation.",
"admin.editors.markdown.plantumlServerUrl": "PlantUML Server URL",
"admin.editors.markdown.plantumlServerUrlHint": "URL to the PlantUML server used for image generation.",
"admin.editors.markdown.quotes": "Quotes Style",
"admin.editors.markdown.quotesHint": "When typographer is enabled. Double + single quotes replacement pairs. e.g. «»„“ for Russian, „“‚‘ for German, etc.",
"admin.editors.saveSuccess": "Editors state saved successfully.",
"admin.editors.markdown.saveSuccess": "Markdown editor configuration saved successfully.",
"editor.markup.insertTable": "Insert Table",
"editor.markup.header": "Header",
"profile.cvdHint": "Alter the color scheme of certain UI elements to account for certain color vision dificiencies.",
"profile.cvd": "Color Vision Deficiency",
"profile.cvdNone": "None",
"profile.cvdProtanopia": "Protanopia",
"profile.cvdTritanopia": "Tritanopia",
"profile.cvdDeuteranopia": "Deuteranopia",
"profile.accessibility": "Accessibility",
"editor.toggleScrollSync": "Toggle Scroll Sync",
"editor.togglePreviewPane": "Toggle Preview Pane",
"editor.renderPreview": "Render Preview",
"editor.tableEditor.title": "Table Editor"
"welcome.title": "Welcome to Wiki.js!"
}

@ -149,105 +149,13 @@ q-page.column
icon='las la-thumbs-up'
color='secondary'
)
.page-actions.column.items-stretch.order-last
q-btn.q-py-md(
flat
icon='las la-pen-nib'
color='deep-orange-9'
aria-label='Page Properties'
@click='togglePageProperties'
)
q-tooltip(anchor='center left' self='center right') Page Properties
q-btn.q-py-md(
flat
icon='las la-project-diagram'
color='deep-orange-9'
aria-label='Page Data'
@click='togglePageData'
disable
v-if='flagsStore.experimental'
)
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-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'
auto-close
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')
q-item-section
q-item-label Convert Page
q-item(clickable)
q-item-section.items-center(avatar)
q-icon(color='deep-orange-9', name='las la-magic', size='sm')
q-item-section
q-item-label Re-render Page
q-item(clickable)
q-item-section.items-center(avatar)
q-icon(color='deep-orange-9', name='las la-sun', size='sm')
q-item-section
q-item-label View Backlinks
q-space
q-btn.q-py-sm(
flat
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(
flat
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(
flat
icon='las la-trash'
color='grey'
aria-label='Delete Page'
@click='deletePage'
)
q-tooltip(anchor='center left' self='center right') Delete Page
q-dialog(
v-model='state.showSideDialog'
position='right'
full-height
transition-show='jump-left'
transition-hide='jump-right'
class='floating-sidepanel'
no-shake
)
component(:is='sideDialogs[state.sideDialogComponent]')
page-actions-col
side-dialog
</template>
<script setup>
import { useMeta, useQuasar, setCssVar } from 'quasar'
import { useMeta, useQuasar } from 'quasar'
import { computed, defineAsyncComponent, onMounted, reactive, ref, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
@ -261,19 +169,10 @@ import { useSiteStore } from 'src/stores/site'
// COMPONENTS
import LoadingGeneric from 'src/components/LoadingGeneric.vue'
import PageActionsCol from 'src/components/PageActionsCol.vue'
import PageHeader from 'src/components/PageHeader.vue'
import PageTags from '../components/PageTags.vue'
const sideDialogs = {
PageDataDialog: defineAsyncComponent({
loader: () => import('../components/PageDataDialog.vue'),
loadingComponent: LoadingGeneric
}),
PagePropertiesDialog: defineAsyncComponent({
loader: () => import('../components/PagePropertiesDialog.vue'),
loadingComponent: LoadingGeneric
})
}
import PageTags from 'src/components/PageTags.vue'
import SideDialog from 'src/components/SideDialog.vue'
const editorComponents = {
markdown: defineAsyncComponent({
@ -364,10 +263,14 @@ watch(() => route.path, async (newValue) => {
await pageStore.pageLoad({ path: newValue })
} catch (err) {
if (err.message === 'ERR_PAGE_NOT_FOUND') {
$q.notify({
type: 'negative',
message: 'This page does not exist (yet)!'
})
if (newValue === '/') {
siteStore.overlay = 'Welcome'
} else {
$q.notify({
type: 'negative',
message: 'This page does not exist (yet)!'
})
}
} else {
$q.notify({
type: 'negative',
@ -382,58 +285,6 @@ watch(() => pageStore.tocDepth, () => { refreshTocExpanded() })
// METHODS
function togglePageProperties () {
state.sideDialogComponent = 'PagePropertiesDialog'
state.showSideDialog = true
}
function togglePageData () {
state.sideDialogComponent = 'PageDataDialog'
state.showSideDialog = true
}
function duplicatePage () {
$q.dialog({
component: defineAsyncComponent(() => import('../components/TreeBrowserDialog.vue')),
componentProps: {
mode: 'duplicatePage',
folderPath: '',
itemId: pageStore.id,
itemTitle: pageStore.title,
itemFileName: pageStore.path
}
}).onOk(() => {
// TODO: change route to new location
})
}
function renamePage () {
$q.dialog({
component: defineAsyncComponent(() => import('../components/TreeBrowserDialog.vue')),
componentProps: {
mode: 'renamePage',
folderPath: '',
itemId: pageStore.id,
itemTitle: pageStore.title,
itemFileName: 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) {
const toExpand = []
let isRootNode = false
@ -538,16 +389,6 @@ function refreshTocExpanded (baseToc, lvl) {
}
}
}
.page-actions {
flex: 0 0 56px;
@at-root .body--light & {
background-color: $grey-3;
}
@at-root .body--dark & {
background-color: $dark-4;
}
}
.floating-syncpanel {
.q-dialog__inner {
@ -570,59 +411,6 @@ function refreshTocExpanded (baseToc, lvl) {
}
}
.floating-sidepanel {
.q-dialog__inner {
right: 24px;
.q-card {
border-radius: 4px !important;
min-width: 450px;
.q-card__section {
border-radius: 0;
}
}
}
.alt-card {
@at-root .body--light & {
background-color: $grey-2;
border-top: 1px solid $grey-4;
box-shadow: inset 0 1px 0 0 #FFF, inset 0 -1px 0 0 #FFF;
border-bottom: 1px solid $grey-4;
}
@at-root .body--dark & {
background-color: $dark-4;
border-top: 1px solid lighten($dark-3, 8%);
box-shadow: inset 0 1px 0 0 $dark-6, inset 0 -1px 0 0 $dark-6;
border-bottom: 1px solid lighten($dark-3, 8%);
}
}
&-quickaccess {
width: 40px;
border-radius: 4px !important;
background-color: rgba(0,0,0,.75);
backdrop-filter: blur(5px);
color: #FFF;
position: fixed;
right: 486px;
top: 74px;
z-index: -1;
display: flex;
flex-direction: column;
box-shadow: 0 0 5px 0 rgba(0,0,0,.5) !important;
@at-root .q-transition--jump-left-enter-active & {
display: none !important;
}
@at-root .q-transition--jump-right-leave-active & {
display: none !important;
}
}
}
.q-card {
@at-root .body--light & {
background-color: #FFF;

@ -1,113 +0,0 @@
<template lang='pug'>
.welcome
.welcome-bg
.welcome-content
.welcome-logo
img(src='/_assets/logo-wikijs.svg')
.welcome-title {{t('welcome.title')}}
.welcome-subtitle {{t('welcome.subtitle')}}
.welcome-actions
q-btn(
push
color='primary'
:label='t(`welcome.createHome`)'
icon='las la-plus'
no-caps
href='/_edit'
)
q-btn(
push
color='primary'
:label='t(`welcome.admin`)'
icon='las la-cog'
no-caps
to='/_admin'
)
</template>
<script setup>
import { useI18n } from 'vue-i18n'
import { useMeta } from 'quasar'
// I18N
const { t } = useI18n()
// META
useMeta({
title: t('welcome.title')
})
</script>
<style lang="scss">
.welcome {
background: $dark-6 radial-gradient(ellipse, $dark-4, $dark-6);
color: #FFF;
height: 100vh;
&-bg {
position: absolute;
top: 50%;
left: 50%;
width: 320px;
height: 320px;
background: linear-gradient(0, $purple-6 50%, $blue-9 50%);
border-radius: 50%;
filter: blur(80px);
transform: translate(-50%, -50%);
}
&-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 90vw;
}
&-logo {
user-select: none;
> img {
height: 200px;
user-select: none;
}
}
&-title {
font-size: 4rem;
font-weight: 500;
line-height: 4rem;
text-align: center;
@media (max-width: $breakpoint-md-max) {
font-size: 2.5rem;
line-height: 2.5rem;
}
}
&-subtitle {
font-size: 1.2rem;
font-weight: 500;
color: $purple-2;
line-height: 1.2rem;
margin-top: 1rem;
}
&-actions {
margin-top: 2rem;
text-align: center;
> .q-btn {
margin: 0 5px 5px 5px;
}
}
}
</style>

@ -4,8 +4,8 @@ const routes = [
path: '/',
component: () => import('../layouts/MainLayout.vue'),
children: [
{ path: '', component: () => import('../pages/Index.vue') }
// { path: 'n/:editor?', component: () => import('../pages/Index.vue') }
{ path: '', component: () => import('../pages/Index.vue') },
{ path: '_create/:editor?', component: () => import('../pages/Index.vue') }
]
},
{
@ -61,10 +61,6 @@ const routes = [
{ path: 'flags', component: () => import('pages/AdminFlags.vue') }
]
},
{
path: '/_welcome',
component: () => import('pages/Welcome.vue')
},
{
path: '/_error/:action?',
component: () => import('pages/ErrorGeneric.vue')

@ -191,7 +191,7 @@ export const usePageStore = defineStore('page', {
// -> Page Data
this.id = 0
this.locale = locale || this.locale
if (path) {
if (path || path === '') {
this.path = path
} else {
this.path = this.path.length < 2 ? 'new-page' : `${this.path}/new-page`
@ -199,7 +199,7 @@ export const usePageStore = defineStore('page', {
this.title = ''
this.description = ''
this.icon = 'las la-file-alt'
this.isPublished = false
this.publishState = 'published'
this.relations = []
this.tags = []

@ -60,6 +60,8 @@ export const useSiteStore = defineStore('site', {
width: '9px',
opacity: 1
},
sideDialogShown: false,
sideDialogComponent: '',
docsBase: 'https://next.js.wiki/docs'
}),
getters: {

Loading…
Cancel
Save