1
0
mirror of https://github.com/Luzifer/wiki.git synced 2024-09-16 14:18:29 +00:00

Port frontend to Vue 3 / Bootstrap 5.3

and update dependencies

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2024-01-30 15:46:01 +01:00
parent e1039e5ae2
commit a70eb9306a
Signed by: luzifer
SSH Key Fingerprint: SHA256:/xtE5lCgiRDQr8SLxHMS92ZBlACmATUmF1crK16Ks4E
19 changed files with 4334 additions and 8409 deletions

152
.eslintrc.js Normal file
View File

@ -0,0 +1,152 @@
/*
* Hack to automatically load globally installed eslint modules
* on Archlinux systems placed in /usr/lib/node_modules
*
* Source: https://github.com/eslint/eslint/issues/11914#issuecomment-569108633
*/
const Module = require('module')
const hacks = [
'@babel/eslint-parser',
'eslint-plugin-vue',
]
const ModuleFindPath = Module._findPath
Module._findPath = (request, paths, isMain) => {
const r = ModuleFindPath(request, paths, isMain)
if (!r && hacks.includes(request)) {
return require.resolve(`/usr/lib/node_modules/${request}`)
}
return r
}
/*
* ESLint configuration derived as differences from eslint:recommended
* with changes I found useful to ensure code quality and equal formatting
* https://eslint.org/docs/user-guide/configuring
*/
module.exports = {
env: {
browser: true,
node: true,
},
extends: [
'plugin:vue/recommended',
'eslint:recommended', // https://eslint.org/docs/rules/
],
globals: {
process: true,
},
parserOptions: {
ecmaVersion: 2020,
parser: '@babel/eslint-parser',
requireConfigFile: false,
},
plugins: [
// required to lint *.vue files
'vue',
],
reportUnusedDisableDirectives: true,
root: true,
rules: {
'array-bracket-newline': ['error', { multiline: true }],
'array-bracket-spacing': ['error'],
'arrow-body-style': ['error', 'as-needed'],
'arrow-parens': ['error', 'as-needed'],
'arrow-spacing': ['error', { after: true, before: true }],
'block-spacing': ['error'],
'brace-style': ['error', '1tbs'],
'camelcase': ['error'],
'comma-dangle': ['error', 'always-multiline'],
'comma-spacing': ['error'],
'comma-style': ['error', 'last'],
'curly': ['error'],
'default-case-last': ['error'],
'default-param-last': ['error'],
'dot-location': ['error', 'property'],
'dot-notation': ['error'],
'eol-last': ['error', 'always'],
'eqeqeq': ['error', 'always', { null: 'ignore' }],
'func-call-spacing': ['error', 'never'],
'function-paren-newline': ['error', 'multiline'],
'generator-star-spacing': ['off'], // allow async-await
'implicit-arrow-linebreak': ['error'],
'indent': ['error', 2],
'key-spacing': ['error', { afterColon: true, beforeColon: false, mode: 'strict' }],
'keyword-spacing': ['error'],
'linebreak-style': ['error', 'unix'],
'lines-between-class-members': ['error'],
'multiline-comment-style': ['warn'],
'newline-per-chained-call': ['error'],
'no-alert': ['error'],
'no-console': ['off'],
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', // allow debugger during development
'no-duplicate-imports': ['error'],
'no-else-return': ['error'],
'no-empty-function': ['error'],
'no-extra-parens': ['error'],
'no-implicit-coercion': ['error'],
'no-lonely-if': ['error'],
'no-multi-spaces': ['error'],
'no-multiple-empty-lines': ['warn', { max: 2, maxBOF: 0, maxEOF: 0 }],
'no-promise-executor-return': ['error'],
'no-return-assign': ['error'],
'no-script-url': ['error'],
'no-template-curly-in-string': ['error'],
'no-trailing-spaces': ['error'],
'no-unneeded-ternary': ['error'],
'no-unreachable-loop': ['error'],
'no-unsafe-optional-chaining': ['error'],
'no-useless-return': ['error'],
'no-var': ['error'],
'no-warning-comments': ['error'],
'no-whitespace-before-property': ['error'],
'object-curly-newline': ['error', { consistent: true }],
'object-curly-spacing': ['error', 'always'],
'object-shorthand': ['error'],
'padded-blocks': ['error', 'never'],
'prefer-arrow-callback': ['error'],
'prefer-const': ['error'],
'prefer-object-spread': ['error'],
'prefer-rest-params': ['error'],
'prefer-template': ['error'],
'quote-props': ['error', 'consistent-as-needed', { keywords: false }],
'quotes': ['error', 'single', { allowTemplateLiterals: true }],
'require-atomic-updates': ['error'],
'require-await': ['error'],
'semi': ['error', 'never'],
'sort-imports': ['error', { ignoreCase: true, ignoreDeclarationSort: false, ignoreMemberSort: false }],
'sort-keys': ['error', 'asc', { caseSensitive: true, natural: false }],
'space-before-blocks': ['error', 'always'],
'space-before-function-paren': ['error', 'never'],
'space-in-parens': ['error', 'never'],
'space-infix-ops': ['error'],
'space-unary-ops': ['error', { nonwords: false, words: true }],
'spaced-comment': ['warn', 'always'],
'switch-colon-spacing': ['error'],
'template-curly-spacing': ['error', 'never'],
'unicode-bom': ['error', 'never'],
'vue/new-line-between-multi-line-property': ['error'],
'vue/no-empty-component-block': ['error'],
'vue/no-reserved-component-names': ['error'],
'vue/no-template-target-blank': ['error'],
'vue/no-unused-properties': ['error'],
'vue/no-unused-refs': ['error'],
'vue/no-useless-mustaches': ['error'],
'vue/order-in-components': ['off'], // Collides with sort-keys
'vue/require-name-property': ['error'],
'vue/v-for-delimiter-style': ['error'],
'vue/v-on-function-call': ['error'],
'wrap-iife': ['error'],
'yoda': ['error'],
},
}

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
data
src/node_modules
node_modules
wiki

30
ci/build.mjs Normal file
View File

@ -0,0 +1,30 @@
import esbuild from 'esbuild'
import { sassPlugin } from 'esbuild-sass-plugin'
import vuePlugin from 'esbuild-plugin-vue3'
esbuild.build({
assetNames: '[name]-[hash]',
bundle: true,
define: {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'dev'),
},
entryPoints: ['src/main.js'],
legalComments: 'none',
loader: {
'.ttf': 'file',
'.woff2': 'file',
},
minify: true,
outfile: 'frontend/app.js',
plugins: [
sassPlugin(),
vuePlugin(),
],
target: [
'chrome87',
'edge87',
'es2020',
'firefox84',
'safari14',
],
})

1
frontend/app.css Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,21 +1,24 @@
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Wiki</title>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Wiki</title>
<div id="app"></div>
<link rel="stylesheet" href="/app.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.10.1/css/all.css">
</head>
<!-- Loading prism from external not to pack ALL languages -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1/themes/prism.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1/themes/prism-okaidia.min.css">
<script src="https://cdn.jsdelivr.net/npm/prismjs@1/prism.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1/plugins/autoloader/prism-autoloader.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1/plugins/line-numbers/prism-line-numbers.min.js"></script>
<body>
<div id="app"></div>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.10.1/css/all.css">
<!-- Loading prism from external not to pack ALL languages -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1/themes/prism.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1/themes/prism-okaidia.min.css">
<script src="https://cdn.jsdelivr.net/npm/prismjs@1/prism.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1/plugins/autoloader/prism-autoloader.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1/plugins/line-numbers/prism-line-numbers.min.js"></script>
<script src="/app.js"></script>
<script src="/app.js"></script>
</body>
</html>

View File

@ -88,7 +88,7 @@ func main() {
}
func handleIndexPage(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/app.js" {
if r.URL.Path != "/app.js" && r.URL.Path != "/app.css" {
r.URL.Path = "/index.html"
}

3553
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

19
package.json Normal file
View File

@ -0,0 +1,19 @@
{
"devDependencies": {
"@babel/eslint-parser": "^7.23.3",
"esbuild": "^0.19.11",
"esbuild-plugin-vue3": "^0.4.0",
"esbuild-sass-plugin": "^2.16.1",
"eslint": "^8.56.0",
"eslint-plugin-vue": "^9.20.1"
},
"name": "wiki",
"private": true,
"dependencies": {
"bootstrap": "^5.3.2",
"easymde": "^2.18.0",
"showdown": "^2.1.0",
"vue": "^3.4.14",
"vue-router": "^4.2.5"
}
}

View File

@ -1,88 +0,0 @@
// https://eslint.org/docs/user-guide/configuring
module.exports = {
'root': true,
'parserOptions': {
parser: 'babel-eslint',
sourceType: 'module',
},
'env': {
node: true,
},
'extends': [
/*
* https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
* consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
*/
'plugin:vue/recommended',
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
'eslint:recommended',
],
// required to lint *.vue files
'plugins': ['vue'],
'globals': {
process: true,
Prism: true,
},
// add your custom rules here
'rules': {
'array-bracket-newline': ['error', { multiline: true }],
'array-bracket-spacing': ['error'],
'arrow-body-style': ['error', 'as-needed'],
'arrow-parens': ['error', 'as-needed'],
'arrow-spacing': ['error', { before: true, after: true }],
'block-spacing': ['error'],
'brace-style': ['error', '1tbs'],
'comma-dangle': ['error', 'always-multiline'], // Apply Contentflow rules
'comma-spacing': ['error'],
'comma-style': ['error', 'last'],
'curly': ['error'],
'dot-location': ['error', 'property'],
'dot-notation': ['error'],
'eol-last': ['error', 'always'],
'eqeqeq': ['error', 'always', { 'null': 'ignore' }],
'func-call-spacing': ['error', 'never'],
'function-paren-newline': ['error', 'multiline'],
'generator-star-spacing': ['off'], // allow async-await
'implicit-arrow-linebreak': ['error'],
'indent': ['error', 2],
'key-spacing': ['error', { beforeColon: false, afterColon: true, mode: 'strict' }],
'keyword-spacing': ['error'],
'linebreak-style': ['error', 'unix'],
'lines-between-class-members': ['error'],
'multiline-comment-style': ['warn'],
'newline-per-chained-call': ['error'],
'no-console': ['off'],
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', // allow debugger during development
'no-else-return': ['error'],
'no-extra-parens': ['error'],
'no-implicit-coercion': ['error'],
'no-lonely-if': ['error'],
'no-multiple-empty-lines': ['warn', { max: 2, maxEOF: 0, maxBOF: 0 }],
'no-multi-spaces': ['error'],
'no-trailing-spaces': ['error'],
'no-unneeded-ternary': ['error'],
'no-useless-return': ['error'],
'no-whitespace-before-property': ['error'],
'object-curly-newline': ['error', { consistent: true }],
'object-curly-spacing': ['error', 'always'],
'object-shorthand': ['error'],
'padded-blocks': ['error', 'never'],
'prefer-arrow-callback': ['error'],
'prefer-const': ['error'],
'prefer-object-spread': ['error'],
'prefer-template': ['error'],
'quote-props': ['error', 'consistent-as-needed', { keywords: true }],
'quotes': ['error', 'single', { allowTemplateLiterals: true }],
'semi': ['error', 'never'],
'space-before-blocks': ['error', 'always'],
'spaced-comment': ['warn', 'always'],
'space-infix-ops': ['error'],
'space-in-parens': ['error', 'never'],
'space-unary-ops': ['error', { words: true, nonwords: false }],
'switch-colon-spacing': ['error'],
'unicode-bom': ['error', 'never'],
'wrap-iife': ['error'],
'yoda': ['error'],
},
}

View File

@ -1,76 +1,76 @@
<template>
<div>
<b-navbar
type="dark"
variant="primary"
class="mb-4"
>
<b-navbar-brand
href="/"
@click.prevent="$router.push({ name: 'home' })"
>
Wiki
</b-navbar-brand>
<b-navbar-nav>
<b-nav-item
v-for="page in navContent"
:key="page"
:to="{ name: 'view', params: { page } }"
<nav class="navbar navbar-expand-lg bg-body-tertiary mb-3">
<div class="container-fluid">
<router-link
class="navbar-brand"
:to="{ name: 'home' }"
>
{{ page }}
</b-nav-item>
</b-navbar-nav>
<template ref="nav">
<vue-markdown
class="nav"
:source="navContent"
:prerender="prerender"
@rendered="rendered"
/>
</template>
</b-navbar>
Wiki
</router-link>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon" />
</button>
<div
id="navbarSupportedContent"
class="collapse navbar-collapse"
>
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li
v-for="page in navContent"
:key="page"
class="nav-item"
>
<router-link
class="nav-link"
:to="{ name: 'view', params: { page } }"
>
{{ page }}
</router-link>
</li>
</ul>
</div>
</div>
</nav>
<router-view />
</div>
</template>
<script>
import axios from 'axios'
import VueMarkdown from 'vue-markdown'
export default {
name: 'App',
components: {
VueMarkdown,
},
data() {
return {
navContent: '',
}
},
mounted() {
this.loadNav()
},
methods: {
loadNav() {
axios.get(`/_content/_navigation`)
.then(resp => {
this.navContent = resp.data.content.split('\n')
.filter(el => !el.match(/^!/))
return fetch(`/_content/_navigation`)
.then(resp => resp.json())
.then(data => {
this.navContent = data.content.split('\n')
.filter(el => el && !el.match(/^!/))
})
.catch(err => {
if (err.response && err.response.status === 404) {
return
}
console.error(err)
})
},
},
mounted() {
this.loadNav()
},
name: 'WikiApp',
}
</script>

82
src/easymde.css Normal file
View File

@ -0,0 +1,82 @@
/*
* Custom CSS to make EasyMDE play nice with Bootstrap colors
* https://github.com/Ionaru/easy-markdown-editor/issues/131
*/
.EasyMDEContainer .CodeMirror {
color: var(--bs-body-color);
border-color: var(--bs-border-color);
background-color: var(--bs-body-bg);
}
.EasyMDEContainer .cm-s-easymde .CodeMirror-cursor {
border-color: var(--bs-body-color);
}
.CodeMirror-cursor {
border-left: 1px solid var(--bs-body-color);
border-right: none;
width: 0;
}
.EasyMDEContainer .editor-toolbar>* {
border-color: var(--bs-body-bg);
}
.editor-toolbar {
border-top: 1px solid var(--bs-border-color);
border-left: 1px solid var(--bs-border-color);
border-right: 1px solid var(--bs-border-color);
}
.editor-toolbar i.separator {
border-left: 1px solid var(--bs-border-color);
border-right: 1px solid var(--bs-border-color);
}
.EasyMDEContainer .editor-toolbar>.active,
.editor-toolbar>button:hover,
.editor-preview pre,
.cm-s-easymde .cm-comment {
background-color: var(--bs-body-bg);
}
.EasyMDEContainer .CodeMirror-fullscreen {
background: var(--bs-body-bg);
}
.editor-toolbar.fullscreen {
background: var(--bs-body-bg);
}
.editor-preview {
background: var(--bs-body-bg);
}
.editor-preview-side {
border-color: var(--bs-border-color);
}
.CodeMirror-selected {
background: var(--bs-secondary-bg);
}
.CodeMirror-focused .CodeMirror-selected {
background: var(--bs-secondary-bg);
}
.CodeMirror-line::selection,
.CodeMirror-line>span::selection,
.CodeMirror-line>span>span::selection {
background: var(--bs-secondary-bg)
}
.CodeMirror-line::-moz-selection,
.CodeMirror-line>span::-moz-selection,
.CodeMirror-line>span>span::-moz-selection {
background: var(--bs-secondary-bg)
}
.EasyMDEContainer .CodeMirror-focused .CodeMirror-selected {
background: var(--bs-secondary-bg)
}

View File

@ -1,33 +1,84 @@
<template>
<b-container>
<b-row>
<b-col>
<div class="container">
<div class="row">
<div class="col">
<textarea ref="editor" />
<b-btn
variant="primary"
<button
class="btn btn-primary"
@click="save"
>
Save
</b-btn>
</b-col>
</b-row>
</b-container>
</button>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import EasyMDE from 'easymde'
export default {
name: 'Edit',
data() {
return {
editor: null,
}
},
methods: {
loadPage(pageName) {
console.debug(`Loading ${pageName}...`)
return fetch(`/_content/${pageName}`)
.then(resp => {
if (resp.status === 404) {
return { content: `# ${pageName}` }
}
return resp.json()
})
.then(data => {
if (this.editor) {
this.editor.toTextArea()
this.editor = null
}
this.editor = new EasyMDE({
element: this.$refs.editor,
forceSync: true,
indentWithTabs: false,
initialValue: data.content,
})
// this.editor.codemirror.setValue(data.content)
})
.catch(err => {
if (err.response && err.response.status === 404) {
return
}
console.error(err)
})
},
save() {
return fetch(`/_content/${this.$route.params.page}`, {
body: JSON.stringify({ content: this.$refs.editor.value }),
method: 'POST',
})
.then(() => {
this.$router.push({ name: 'view', params: { page: this.$route.params.page } })
})
.catch(err => {
console.error(err)
})
},
},
mounted() {
this.loadPage(this.$route.params.page)
},
name: 'WikiEdit',
watch: {
'$route'(to, from) {
if (to.params.page === from.params.page) {
@ -37,53 +88,5 @@ export default {
this.loadPage(to.params.page)
},
},
mounted() {
if (!this.editor) {
this.editor = new EasyMDE({
element: this.$refs.editor,
forceSync: true,
indentWithTabs: false,
})
window.editor = this.editor
}
this.loadPage(this.$route.params.page)
},
methods: {
loadPage(pageName) {
console.debug(`Loading ${pageName}...`)
axios.get(`/_content/${pageName}`)
.then(resp => {
this.editor.codemirror.setValue(resp.data.content)
})
.catch(err => {
if (err.response && err.response.status === 404) {
return
}
console.error(err)
// FIXME: Show error
})
},
save() {
axios.post(`/_content/${this.$route.params.page}`, {
content: this.$refs.editor.value,
})
.then(() => {
this.$router.push({ name: 'view', params: { page: this.$route.params.page } })
})
.catch(err => {
console.error(err)
})
},
},
}
</script>
<style>
.editor-toolbar {
background-color: #ccc;
}
</style>

View File

@ -1,19 +1,13 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import 'bootswatch/dist/darkly/bootstrap.css'
import 'easymde/dist/easymde.min.css'
import './easymde.css'
import { createApp, h } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './app.vue'
import View from './view.vue'
import Edit from './edit.vue'
Vue.use(BootstrapVue)
Vue.use(VueRouter)
import View from './view.vue'
const routes = [
{
@ -33,15 +27,20 @@ const routes = [
},
]
const router = new VueRouter({
const router = createRouter({
history: createWebHistory(),
mode: 'history',
routes,
})
new Vue({
el: '#app',
components: { App },
data: { },
render: createElement => createElement('app'),
const app = createApp({
name: 'WikiMain',
render() {
return h(App)
},
router,
})
app.use(router)
app.mount('#app')

45
src/markdown.vue Normal file
View File

@ -0,0 +1,45 @@
<template>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-html="render" />
</template>
<script>
import showdown from 'showdown'
export default {
data() {
return {
render: '',
}
},
emits: ['rendered'],
name: 'WikiMarkdown',
props: {
content: {
default: '',
type: String,
},
prerender: {
default: null,
type: Function,
},
},
watch: {
content(to) {
let content = to
if (this.prerender) {
content = this.prerender(content)
}
const converter = new showdown.Converter()
this.render = converter.makeHtml(content)
this.$emit('rendered')
},
},
}
</script>

7985
src/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,43 +0,0 @@
{
"devDependencies": {
"babel-core": "^6.26.3",
"babel-eslint": "^10.1.0",
"babel-loader": "^7.1.5",
"babel-preset-env": "^1.7.0",
"css-loader": "^1.0.0",
"easymde": "^2.11.0",
"eslint": "^5.13.0",
"eslint-config-standard": "^12.0.0",
"eslint-friendly-formatter": "^4.0.1",
"eslint-loader": "^2.1.1",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-node": "^8.0.1",
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-standard": "^4.0.1",
"eslint-plugin-vue": "^5.2.3",
"node-sass": "^4.14.1",
"sass-loader": "^7.3.1",
"style-loader": "^0.21.0",
"vue-loader": "^15.9.3",
"vue-markdown": "^2.2.4",
"vue-router": "^3.3.4",
"vue-template-compiler": "^2.6.11",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-dev-middleware": "^1.4.0",
"webpack-hot-middleware": "^2.9.1"
},
"name": "wiki",
"private": true,
"scripts": {
"build": "webpack -p"
},
"dependencies": {
"axios": "^0.19.2",
"bootstrap": "^4.5.0",
"bootstrap-vue": "^2.16.0",
"bootswatch": "^4.5.0",
"popper.js": "^1.16.1",
"vue": "^2.6.11"
}
}

View File

@ -1,37 +1,37 @@
<template>
<b-container>
<b-row>
<b-col
<div class="container">
<div class="row">
<div
ref="content"
class="relAnchor"
class="col relAnchor"
>
<b-btn
class="editBtn"
variant="secondary"
size="sm"
<router-link
v-if="$route.params.page"
class="btn btn-secondary btn-sm editBtn"
:to="{ name: 'edit', params: { page: $route.params.page } }"
>
<i class="fas fa-edit" />
</b-btn>
<vue-markdown
:source="content"
</router-link>
<md-render
:content="content"
:prerender="prerender"
@rendered="rendered"
/>
</b-col>
</b-row>
</b-container>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import VueMarkdown from 'vue-markdown'
/* global Prism */
import mdRender from './markdown.vue'
export default {
name: 'View',
components: {
VueMarkdown,
mdRender,
},
data() {
@ -40,20 +40,6 @@ export default {
}
},
watch: {
'$route'(to, from) {
if (to.params.page === from.params.page) {
return
}
this.loadPage(to.params.page)
},
},
mounted() {
this.loadPage(this.$route.params.page)
},
methods: {
intLinkClick(evt) {
const link = evt.target
@ -63,23 +49,33 @@ export default {
loadPage(pageName) {
console.debug(`Loading ${pageName}...`)
axios.get(`/_content/${pageName}`)
return fetch(`/_content/${pageName}`)
.then(resp => {
this.content = resp.data.content
})
.catch(err => {
if (err.response && err.response.status === 404) {
if (resp.status === 404) {
this.$router.push({ name: 'edit', params: { page: pageName } })
return
}
return resp.json()
})
.then(data => {
if (!data) {
return
}
this.content = data.content
})
.catch(err => {
console.error(err)
// FIXME: Show error
})
},
prerender(mdtext) {
// replace [[Internal]] links
mdtext = mdtext.replace(new RegExp(/\[\[([^\]]+)\]\]/, 'g'), '<a class="intLink" data-page="$1" href="$1">$1</a>')
mdtext = mdtext.replace(
new RegExp(/\[\[([^\]]+)\]\]/, 'g'),
'<a class="intLink" data-page="$1" href="$1">$1</a>',
)
return mdtext
},
@ -98,6 +94,22 @@ export default {
}, 100)
},
},
mounted() {
this.loadPage(this.$route.params.page)
},
name: 'WikiView',
watch: {
'$route'(to, from) {
if (to.params.page === from.params.page) {
return
}
this.loadPage(to.params.page)
},
},
}
</script>

View File

@ -1,54 +0,0 @@
const path = require('path')
const webpack = require('webpack')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
entry: './main.js',
output: {
filename: 'app.js',
path: path.resolve(__dirname, '..', 'frontend'),
},
plugins: [
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production'),
},
}),
new VueLoaderPlugin(),
],
optimization: {
minimize: true,
},
module: {
rules: [
{
test: /\.(s?)css$/,
use: [
'style-loader',
'css-loader',
'sass-loader',
],
},
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: [['env', { targets: { browsers: ['>0.25%', 'not ie 11', 'not op_mini all'] } }]],
},
},
},
{
test: /\.vue$/,
loader: 'vue-loader',
},
],
},
}