mirror of
https://github.com/Luzifer/wiki.git
synced 2024-12-20 02:21:18 +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:
parent
e1039e5ae2
commit
a70eb9306a
19 changed files with 4334 additions and 8409 deletions
152
.eslintrc.js
Normal file
152
.eslintrc.js
Normal 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
2
.gitignore
vendored
|
@ -1,3 +1,3 @@
|
|||
data
|
||||
src/node_modules
|
||||
node_modules
|
||||
wiki
|
||||
|
|
30
ci/build.mjs
Normal file
30
ci/build.mjs
Normal 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
1
frontend/app.css
Normal file
File diff suppressed because one or more lines are too long
308
frontend/app.js
308
frontend/app.js
File diff suppressed because one or more lines are too long
|
@ -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>
|
||||
|
||||
|
|
2
main.go
2
main.go
|
@ -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
3553
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
19
package.json
Normal file
19
package.json
Normal 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"
|
||||
}
|
||||
}
|
|
@ -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'],
|
||||
},
|
||||
}
|
100
src/app.vue
100
src/app.vue
|
@ -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
82
src/easymde.css
Normal 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)
|
||||
}
|
123
src/edit.vue
123
src/edit.vue
|
@ -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>
|
||||
|
|
33
src/main.js
33
src/main.js
|
@ -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
45
src/markdown.vue
Normal 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
7985
src/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -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"
|
||||
}
|
||||
}
|
90
src/view.vue
90
src/view.vue
|
@ -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>
|
||||
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
}
|
Loading…
Reference in a new issue