StaticGen update Bash
Update zh-cn.yaml (#194) Address #184 build assets Update zh-cn.yaml (#194)pull/196/head
parent
9819c87853
commit
da4fd84002
|
@ -5,6 +5,7 @@
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||||
<meta name="base" content="{{ .BaseURL }}">
|
<meta name="base" content="{{ .BaseURL }}">
|
||||||
|
<meta name="staticgen" content="{{ .StaticGen }}">
|
||||||
<title>File Manager</title>
|
<title>File Manager</title>
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ .BaseURL }}/static/img/icons/favicon-32x32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="{{ .BaseURL }}/static/img/icons/favicon-32x32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ .BaseURL }}/static/img/icons/favicon-16x16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="{{ .BaseURL }}/static/img/icons/favicon-16x16.png">
|
||||||
|
@ -26,8 +27,6 @@
|
||||||
if (file.match(/\.(js|css)$/)) { %>
|
if (file.match(/\.(js|css)$/)) { %>
|
||||||
<link rel="preload" href="{{ .BaseURL }}/<%= file %>" as="<%= file.match(/\.css$/)?'style':'script' %>"><% }}} %>
|
<link rel="preload" href="{{ .BaseURL }}/<%= file %>" as="<%= file.match(/\.css$/)?'style':'script' %>"><% }}} %>
|
||||||
|
|
||||||
<!-- Plugins info -->
|
|
||||||
<script>{{ .JavaScript }}</script>
|
|
||||||
<style>
|
<style>
|
||||||
#loading {
|
#loading {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
|
@ -16,19 +16,11 @@
|
||||||
<i class="material-icons">save</i>
|
<i class="material-icons">save</i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div v-for="plugin in plugins" :key="plugin.name">
|
<template v-if="staticGen.length > 0">
|
||||||
<button class="action"
|
<button v-show="showPublishButton" :aria-label="$t('buttons.publish')" :title="$t('buttons.publish')" class="action" id="publish-button">
|
||||||
v-for="action in plugin.header.visible"
|
<i class="material-icons">send</i>
|
||||||
v-if="action.if(pluginData, $route)"
|
|
||||||
@click="action.click($event, pluginData, $route)"
|
|
||||||
:aria-label="action.name"
|
|
||||||
:id="action.id"
|
|
||||||
:title="action.name"
|
|
||||||
:key="action.name">
|
|
||||||
<i class="material-icons">{{ action.icon }}</i>
|
|
||||||
<span>{{ action.name }}</span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</template>
|
||||||
|
|
||||||
<button @click="openMore" id="more" :aria-label="$t('buttons.more')" :title="$t('buttons.more')" class="action">
|
<button @click="openMore" id="more" :aria-label="$t('buttons.more')" :title="$t('buttons.more')" class="action">
|
||||||
<i class="material-icons">more_vert</i>
|
<i class="material-icons">more_vert</i>
|
||||||
|
@ -52,19 +44,9 @@
|
||||||
<delete-button v-show="showDeleteButton"></delete-button>
|
<delete-button v-show="showDeleteButton"></delete-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-for="plugin in plugins" :key="plugin.name">
|
<template v-if="staticGen.length > 0">
|
||||||
<button class="action"
|
<schedule-button v-show="showPublishButton"></schedule-button>
|
||||||
v-for="action in plugin.header.hidden"
|
</template>
|
||||||
v-if="action.if(pluginData, $route)"
|
|
||||||
@click="action.click($event, pluginData, $route)"
|
|
||||||
:id="action.id"
|
|
||||||
:aria-label="action.name"
|
|
||||||
:title="action.name"
|
|
||||||
:key="action.name">
|
|
||||||
<i class="material-icons">{{ action.icon }}</i>
|
|
||||||
<span>{{ action.name }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<switch-button v-show="showSwitchButton"></switch-button>
|
<switch-button v-show="showSwitchButton"></switch-button>
|
||||||
<download-button v-show="showCommonButton"></download-button>
|
<download-button v-show="showCommonButton"></download-button>
|
||||||
|
@ -91,6 +73,7 @@ import DownloadButton from './buttons/Download'
|
||||||
import SwitchButton from './buttons/SwitchView'
|
import SwitchButton from './buttons/SwitchView'
|
||||||
import MoveButton from './buttons/Move'
|
import MoveButton from './buttons/Move'
|
||||||
import CopyButton from './buttons/Copy'
|
import CopyButton from './buttons/Copy'
|
||||||
|
import ScheduleButton from './buttons/Schedule'
|
||||||
import {mapGetters, mapState} from 'vuex'
|
import {mapGetters, mapState} from 'vuex'
|
||||||
import * as api from '@/utils/api'
|
import * as api from '@/utils/api'
|
||||||
import buttons from '@/utils/buttons'
|
import buttons from '@/utils/buttons'
|
||||||
|
@ -106,7 +89,8 @@ export default {
|
||||||
CopyButton,
|
CopyButton,
|
||||||
UploadButton,
|
UploadButton,
|
||||||
SwitchButton,
|
SwitchButton,
|
||||||
MoveButton
|
MoveButton,
|
||||||
|
ScheduleButton
|
||||||
},
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
|
@ -134,7 +118,7 @@ export default {
|
||||||
'loading',
|
'loading',
|
||||||
'reload',
|
'reload',
|
||||||
'multiple',
|
'multiple',
|
||||||
'plugins'
|
'staticGen'
|
||||||
]),
|
]),
|
||||||
isMobile () {
|
isMobile () {
|
||||||
return this.width <= 736
|
return this.width <= 736
|
||||||
|
@ -148,6 +132,9 @@ export default {
|
||||||
showSaveButton () {
|
showSaveButton () {
|
||||||
return (this.req.kind === 'editor' && !this.loading)
|
return (this.req.kind === 'editor' && !this.loading)
|
||||||
},
|
},
|
||||||
|
showPublishButton () {
|
||||||
|
return (this.req.kind === 'editor' && !this.loading && this.user.allowPublish)
|
||||||
|
},
|
||||||
showSwitchButton () {
|
showSwitchButton () {
|
||||||
return this.req.kind === 'listing' && this.$route.name === 'Files' && !this.loading
|
return this.req.kind === 'listing' && this.$route.name === 'Files' && !this.loading
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,10 +17,32 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-for="plugin in plugins" :key="plugin.name">
|
<div v-if="staticGen.length > 0">
|
||||||
<button v-for="action in plugin.sidebar" @click="action.click($event, pluginData, $route)" :aria-label="action.name" :title="action.name" :key="action.name" class="action">
|
<router-link to="/files/settings"
|
||||||
<i class="material-icons">{{ action.icon }}</i>
|
:aria-label="$t('sidebar.siteSettings')"
|
||||||
<span>{{ action.name }}</span>
|
:title="$t('sidebar.siteSettings')"
|
||||||
|
class="action">
|
||||||
|
<i class="material-icons">settings</i>
|
||||||
|
<span>{{ $t('sidebar.siteSettings') }}</span>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<template v-if="staticGen === 'hugo'">
|
||||||
|
<button class="action"
|
||||||
|
:aria-label="$t('sidebar.hugoNew')"
|
||||||
|
:title="$t('sidebar.hugoNew')"
|
||||||
|
v-if="user.allowNew"
|
||||||
|
@click="$store.commit('showHover', 'new-archetype')">
|
||||||
|
<i class="material-icons">merge_type</i>
|
||||||
|
<span>{{ $t('sidebar.hugoNew') }}</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<button class="action"
|
||||||
|
:aria-label="$t('sidebar.preview')"
|
||||||
|
:title="$t('sidebar.preview')"
|
||||||
|
@click="preview">
|
||||||
|
<i class="material-icons">remove_red_eye</i>
|
||||||
|
<span>{{ $t('sidebar.preview') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -38,7 +60,6 @@
|
||||||
|
|
||||||
<p class="credits">
|
<p class="credits">
|
||||||
<span>{{ $t('sidebar.servedWith') }} <a rel="noopener noreferrer" href="https://github.com/hacdias/filemanager">File Manager</a>.</span>
|
<span>{{ $t('sidebar.servedWith') }} <a rel="noopener noreferrer" href="https://github.com/hacdias/filemanager">File Manager</a>.</span>
|
||||||
<span v-for="plugin in plugins" :key="plugin.name" v-html="plugin.credits"><br></span>
|
|
||||||
<span><a @click="help">{{ $t('sidebar.help') }}</a></span>
|
<span><a @click="help">{{ $t('sidebar.help') }}</a></span>
|
||||||
</p>
|
</p>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -47,31 +68,22 @@
|
||||||
<script>
|
<script>
|
||||||
import {mapState} from 'vuex'
|
import {mapState} from 'vuex'
|
||||||
import auth from '@/utils/auth'
|
import auth from '@/utils/auth'
|
||||||
import buttons from '@/utils/buttons'
|
|
||||||
import api from '@/utils/api'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'sidebar',
|
name: 'sidebar',
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
pluginData: {
|
|
||||||
api,
|
|
||||||
buttons,
|
|
||||||
'store': this.$store,
|
|
||||||
'router': this.$router
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['user', 'plugins']),
|
...mapState(['user', 'staticGen']),
|
||||||
active () {
|
active () {
|
||||||
return this.$store.state.show === 'sidebar'
|
return this.$store.state.show === 'sidebar'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
help: function () {
|
help () {
|
||||||
this.$store.commit('showHover', 'help')
|
this.$store.commit('showHover', 'help')
|
||||||
},
|
},
|
||||||
|
preview () {
|
||||||
|
window.open(this.$store.state.baseURL + '/preview/')
|
||||||
|
},
|
||||||
logout: auth.logout
|
logout: auth.logout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<template>
|
||||||
|
<button @click="show"
|
||||||
|
:aria-label="$t('buttons.schedule')"
|
||||||
|
:title="$t('buttons.schedule')"
|
||||||
|
id="schedule-button"
|
||||||
|
class="action">
|
||||||
|
<i class="material-icons">alarm</i>
|
||||||
|
<span>{{ $t('buttons.schedule') }}</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'schedule-button',
|
||||||
|
methods: {
|
||||||
|
show: function (event) {
|
||||||
|
this.$store.commit('showHover', 'schedule')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -17,7 +17,7 @@ import buttons from '@/utils/buttons'
|
||||||
export default {
|
export default {
|
||||||
name: 'editor',
|
name: 'editor',
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['req']),
|
...mapState(['req', 'schedule']),
|
||||||
hasMetadata: function () {
|
hasMetadata: function () {
|
||||||
return (this.req.metadata !== undefined && this.req.metadata !== null)
|
return (this.req.metadata !== undefined && this.req.metadata !== null)
|
||||||
}
|
}
|
||||||
|
@ -32,10 +32,20 @@ export default {
|
||||||
created () {
|
created () {
|
||||||
window.addEventListener('keydown', this.keyEvent)
|
window.addEventListener('keydown', this.keyEvent)
|
||||||
document.getElementById('save-button').addEventListener('click', this.save)
|
document.getElementById('save-button').addEventListener('click', this.save)
|
||||||
|
|
||||||
|
let publish = document.getElementById('publish-button')
|
||||||
|
if (publish !== null) {
|
||||||
|
publish.addEventListener('click', this.publish)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
window.removeEventListener('keydown', this.keyEvent)
|
window.removeEventListener('keydown', this.keyEvent)
|
||||||
document.getElementById('save-button').removeEventListener('click', this.save)
|
document.getElementById('save-button').removeEventListener('click', this.save)
|
||||||
|
|
||||||
|
let publish = document.getElementById('publish-button')
|
||||||
|
if (publish !== null) {
|
||||||
|
publish.removeEventListener('click', this.publish)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
if (this.req.content === undefined || this.req.content === null) {
|
if (this.req.content === undefined || this.req.content === null) {
|
||||||
|
@ -102,22 +112,30 @@ export default {
|
||||||
this.metalang = 'toml'
|
this.metalang = 'toml'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// Publishes the file.
|
||||||
|
publish (event) {
|
||||||
|
this.save(event, true)
|
||||||
|
},
|
||||||
// Saves the file.
|
// Saves the file.
|
||||||
save () {
|
save (event, regenerate = false) {
|
||||||
buttons.loading('save')
|
let button = regenerate ? 'publish' : 'save'
|
||||||
|
if (this.schedule !== '') button = 'schedule'
|
||||||
let content = this.content.getValue()
|
let content = this.content.getValue()
|
||||||
|
buttons.loading(button)
|
||||||
|
|
||||||
if (this.hasMetadata) {
|
if (this.hasMetadata) {
|
||||||
content = this.metadata.getValue() + '\n\n' + content
|
content = this.metadata.getValue() + '\n\n' + content
|
||||||
}
|
}
|
||||||
|
|
||||||
api.put(this.$route.path, content)
|
api.put(this.$route.path, content, regenerate, this.schedule)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
buttons.done('save')
|
buttons.done(button)
|
||||||
|
this.$store.commit('setSchedule', '')
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
buttons.done('save')
|
buttons.done(button)
|
||||||
this.$store.commit('showError', error)
|
this.$store.commit('showError', error)
|
||||||
|
this.$store.commit('setSchedule', '')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
<template>
|
||||||
|
<div class="prompt">
|
||||||
|
<h3>{{ $t('prompts.newFile') }}</h3>
|
||||||
|
<p>{{ $t('prompts.newArchetype') }}</p>
|
||||||
|
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
||||||
|
<input type="text" @keyup.enter="submit" v-model.trim="archetype">
|
||||||
|
<div>
|
||||||
|
<button class="ok"
|
||||||
|
@click="submit"
|
||||||
|
:aria-label="$t('buttons.create')"
|
||||||
|
:title="$t('buttons.create')">{{ $t('buttons.create') }}</button>
|
||||||
|
<button class="cancel"
|
||||||
|
@click="$store.commit('closeHovers')"
|
||||||
|
:aria-label="$t('buttons.cancel')"
|
||||||
|
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { removePrefix } from '@/utils/api'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'new-archetype',
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
name: '',
|
||||||
|
archetype: 'default'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submit: function (event) {
|
||||||
|
event.preventDefault()
|
||||||
|
this.$store.commit('closeHovers')
|
||||||
|
|
||||||
|
this.new('/' + this.name, this.archetype)
|
||||||
|
.then((url) => {
|
||||||
|
this.$router.push({ path: url })
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.$store.commit('showError', error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
new (url, type) {
|
||||||
|
url = removePrefix(url)
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let request = new window.XMLHttpRequest()
|
||||||
|
request.open('POST', `${this.$store.state.baseURL}/api/resource${url}`, true)
|
||||||
|
request.setRequestHeader('Authorization', `Bearer ${this.$store.state.jwt}`)
|
||||||
|
request.setRequestHeader('Archetype', encodeURIComponent(type))
|
||||||
|
|
||||||
|
request.onload = () => {
|
||||||
|
if (request.status === 200) {
|
||||||
|
resolve(request.getResponseHeader('Location'))
|
||||||
|
} else {
|
||||||
|
reject(request.responseText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request.onerror = (error) => reject(error)
|
||||||
|
request.send()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
|
@ -12,33 +12,8 @@
|
||||||
<error v-else-if="showError"></error>
|
<error v-else-if="showError"></error>
|
||||||
<success v-else-if="showSuccess"></success>
|
<success v-else-if="showSuccess"></success>
|
||||||
<replace v-else-if="showReplace"></replace>
|
<replace v-else-if="showReplace"></replace>
|
||||||
|
<schedule v-else-if="show === 'schedule'"></schedule>
|
||||||
<template v-for="plugin in plugins">
|
<new-archetype v-else-if="show === 'new-archetype'"></new-archetype>
|
||||||
<form class="prompt"
|
|
||||||
v-for="prompt in plugin.prompts"
|
|
||||||
:key="prompt.name"
|
|
||||||
v-if="show === prompt.name"
|
|
||||||
@submit="prompt.submit($event, pluginData, $route)">
|
|
||||||
<h3>{{ prompt.title }}</h3>
|
|
||||||
<p>{{ prompt.description }}</p>
|
|
||||||
<input v-for="input in prompt.inputs"
|
|
||||||
:key="input.name"
|
|
||||||
:type="input.type"
|
|
||||||
:name="input.name"
|
|
||||||
:placeholder="input.placeholder">
|
|
||||||
<div>
|
|
||||||
<input type="submit" class="ok"
|
|
||||||
:aria-label="prompt.ok"
|
|
||||||
:title="prompt.ok"
|
|
||||||
:value="prompt.ok">
|
|
||||||
<button class="cancel"
|
|
||||||
@click="$store.commit('closeHovers')"
|
|
||||||
:aria-label="$t('buttons.cancel')"
|
|
||||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
|
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -55,7 +30,9 @@ import Error from './Error'
|
||||||
import Success from './Success'
|
import Success from './Success'
|
||||||
import NewFile from './NewFile'
|
import NewFile from './NewFile'
|
||||||
import NewDir from './NewDir'
|
import NewDir from './NewDir'
|
||||||
|
import NewArchetype from './NewArchetype'
|
||||||
import Replace from './Replace'
|
import Replace from './Replace'
|
||||||
|
import Schedule from './Schedule'
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import buttons from '@/utils/buttons'
|
import buttons from '@/utils/buttons'
|
||||||
import api from '@/utils/api'
|
import api from '@/utils/api'
|
||||||
|
@ -65,6 +42,8 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
Info,
|
Info,
|
||||||
Delete,
|
Delete,
|
||||||
|
NewArchetype,
|
||||||
|
Schedule,
|
||||||
Rename,
|
Rename,
|
||||||
Error,
|
Error,
|
||||||
Download,
|
Download,
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
<template>
|
||||||
|
<div class="prompt">
|
||||||
|
<h3>{{ $t('prompts.schedule') }}</h3>
|
||||||
|
<p>{{ $t('prompts.scheduleMessage') }}</p>
|
||||||
|
<input autofocus type="datetime-local" v-model="date">
|
||||||
|
<div>
|
||||||
|
<button class="ok"
|
||||||
|
@click="submit"
|
||||||
|
:aria-label="$t('buttons.schedule')"
|
||||||
|
:title="$t('buttons.schedule')">{{ $t('buttons.schedule') }}</button>
|
||||||
|
<button class="cancel"
|
||||||
|
@click="close"
|
||||||
|
:aria-label="$t('buttons.cancel')"
|
||||||
|
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'schedule',
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
date: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
close () {
|
||||||
|
this.$store.commit('closeHovers')
|
||||||
|
},
|
||||||
|
submit: function (event) {
|
||||||
|
event.preventDefault()
|
||||||
|
if (this.date === '') return
|
||||||
|
this.close()
|
||||||
|
this.$store.commit('setSchedule', this.date)
|
||||||
|
document.getElementById('save-button').click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
|
@ -20,7 +20,9 @@ buttons:
|
||||||
save: Save
|
save: Save
|
||||||
search: Search
|
search: Search
|
||||||
select: Select
|
select: Select
|
||||||
|
publish: Publish
|
||||||
selectMultiple: Select multiple
|
selectMultiple: Select multiple
|
||||||
|
schedule: Schedule
|
||||||
switchView: Swicth view
|
switchView: Swicth view
|
||||||
toggleSidebar: Toggle sidebar
|
toggleSidebar: Toggle sidebar
|
||||||
update: Update
|
update: Update
|
||||||
|
@ -93,12 +95,16 @@ prompts:
|
||||||
renameMessage: Insert a new name for
|
renameMessage: Insert a new name for
|
||||||
show: Show
|
show: Show
|
||||||
size: Size
|
size: Size
|
||||||
|
schedule: Schedule
|
||||||
|
scheduleMessage: Pick a date and time to schedule the publication of this post.
|
||||||
|
newArchetype: Create a new post based on an archetype. Your file will be created on content folder.
|
||||||
settings:
|
settings:
|
||||||
admin: Admin
|
admin: Admin
|
||||||
administrator: Administrator
|
administrator: Administrator
|
||||||
allowCommands: Execute commands
|
allowCommands: Execute commands
|
||||||
allowEdit: Edit, rename and delete files or directories.
|
allowEdit: Edit, rename and delete files or directories
|
||||||
allowNew: Create new files and directories
|
allowNew: Create new files and directories
|
||||||
|
allowPublish: Publish new posts and pages
|
||||||
avoidChanges: "(leave blank to avoid changes)"
|
avoidChanges: "(leave blank to avoid changes)"
|
||||||
changePassword: Change Password
|
changePassword: Change Password
|
||||||
commands: Commands
|
commands: Commands
|
||||||
|
@ -122,7 +128,6 @@ settings:
|
||||||
You can set the user to be an administrator or choose the permissions
|
You can set the user to be an administrator or choose the permissions
|
||||||
individually. If you select "Administrator", all of the other options will be
|
individually. If you select "Administrator", all of the other options will be
|
||||||
automatically checked. The management of users remains a privilege of an administrator.
|
automatically checked. The management of users remains a privilege of an administrator.
|
||||||
pluginsUpdated: Plugins settings updated!
|
|
||||||
profileSettings: Profile Settings
|
profileSettings: Profile Settings
|
||||||
ruleExample1: >
|
ruleExample1: >
|
||||||
'prevents the access to any dot file (such as .git, .gitignore) in
|
'prevents the access to any dot file (such as .git, .gitignore) in
|
||||||
|
@ -158,6 +163,9 @@ sidebar:
|
||||||
newFolder: New folder
|
newFolder: New folder
|
||||||
servedWith: Served with
|
servedWith: Served with
|
||||||
settings: Settings
|
settings: Settings
|
||||||
|
siteSettings: Site Settings
|
||||||
|
hugoNew: Hugo New
|
||||||
|
preview: Preview
|
||||||
search:
|
search:
|
||||||
images: Images
|
images: Images
|
||||||
music: Music
|
music: Music
|
||||||
|
|
|
@ -14,10 +14,12 @@ buttons:
|
||||||
next: Próximo
|
next: Próximo
|
||||||
ok: OK
|
ok: OK
|
||||||
previous: Anterior
|
previous: Anterior
|
||||||
|
publish: Publicar
|
||||||
rename: Renomear
|
rename: Renomear
|
||||||
replace: Substituir
|
replace: Substituir
|
||||||
reportIssue: Reportar Erro
|
reportIssue: Reportar Erro
|
||||||
save: Guardar
|
save: Guardar
|
||||||
|
schedule: Agendar
|
||||||
search: Pesquisar
|
search: Pesquisar
|
||||||
select: Selecionar
|
select: Selecionar
|
||||||
selectMultiple: Selecionar múltiplos
|
selectMultiple: Selecionar múltiplos
|
||||||
|
@ -30,11 +32,11 @@ errors:
|
||||||
internal: Algo correu bastante mal.
|
internal: Algo correu bastante mal.
|
||||||
notFound: Não conseguimos chegar a esta localização.
|
notFound: Não conseguimos chegar a esta localização.
|
||||||
files:
|
files:
|
||||||
folders: Pastas
|
|
||||||
files: Ficheiros
|
|
||||||
body: Corpo
|
body: Corpo
|
||||||
clear: Limpar
|
clear: Limpar
|
||||||
closePreview: Fechar pré-visualização
|
closePreview: Fechar pré-visualização
|
||||||
|
files: Ficheiros
|
||||||
|
folders: Pastas
|
||||||
home: Início
|
home: Início
|
||||||
lastModified: Última modificação
|
lastModified: Última modificação
|
||||||
loading: A carregar...
|
loading: A carregar...
|
||||||
|
@ -43,9 +45,9 @@ files:
|
||||||
multipleSelectionEnabled: Seleção múltipla ativada
|
multipleSelectionEnabled: Seleção múltipla ativada
|
||||||
name: Nome
|
name: Nome
|
||||||
size: Tamanho
|
size: Tamanho
|
||||||
|
sortByLastModified: Ordenar pela última modificação
|
||||||
sortByName: Ordenar pelo nome
|
sortByName: Ordenar pelo nome
|
||||||
sortBySize: Ordenar pelo tamanho
|
sortBySize: Ordenar pelo tamanho
|
||||||
sortByLastModified: Ordenar pela última modificação
|
|
||||||
help:
|
help:
|
||||||
click: selecionar pasta ou ficheiro
|
click: selecionar pasta ou ficheiro
|
||||||
ctrl:
|
ctrl:
|
||||||
|
@ -58,6 +60,10 @@ help:
|
||||||
f1: esta informação
|
f1: esta informação
|
||||||
f2: renomear ficheiro
|
f2: renomear ficheiro
|
||||||
help: Ajuda
|
help: Ajuda
|
||||||
|
languages:
|
||||||
|
en: Inglês
|
||||||
|
pt: Português
|
||||||
|
zhCN: Chinês (Simplificado)
|
||||||
login:
|
login:
|
||||||
password: Palavra-passe
|
password: Palavra-passe
|
||||||
submit: Login
|
submit: Login
|
||||||
|
@ -79,6 +85,8 @@ prompts:
|
||||||
lastModified: Última Modificação
|
lastModified: Última Modificação
|
||||||
move: Mover
|
move: Mover
|
||||||
moveMessage: 'Escolha uma nova casa para os seus ficheiros:'
|
moveMessage: 'Escolha uma nova casa para os seus ficheiros:'
|
||||||
|
newArchetype: Criar um novo post baseado num "archetype". O seu ficheiro será criado
|
||||||
|
na pasta "content".
|
||||||
newDir: Nova pasta
|
newDir: Nova pasta
|
||||||
newDirMessage: Escreva o nome da nova pasta.
|
newDirMessage: Escreva o nome da nova pasta.
|
||||||
newFile: Novo ficheiro
|
newFile: Novo ficheiro
|
||||||
|
@ -89,22 +97,38 @@ prompts:
|
||||||
renameMessage: Insira um novo nome para
|
renameMessage: Insira um novo nome para
|
||||||
replace: Substituir
|
replace: Substituir
|
||||||
replaceMessage: >
|
replaceMessage: >
|
||||||
Já existe um ficheiro com nome igual a um dos que está a tentar enviar. Deseja
|
Já existe um ficheiro com nome igual a um dos que está a tentar
|
||||||
substituir?
|
enviar. Deseja substituir?
|
||||||
|
schedule: Agendar
|
||||||
|
scheduleMessage: Escolha uma data para publicar este post.
|
||||||
show: Mostrar
|
show: Mostrar
|
||||||
size: Tamanho
|
size: Tamanho
|
||||||
|
search:
|
||||||
|
images: Imagens
|
||||||
|
music: Música
|
||||||
|
pdf: PDF
|
||||||
|
pressToExecute: Prima enter para executar.
|
||||||
|
pressToSearch: Prima enter para pesquisar.
|
||||||
|
search: Pesquise...
|
||||||
|
searchOrCommand: Pesquise ou execute um comando...
|
||||||
|
searchOrSupportedCommand: 'Pesquise ou utilize um dos seus comandos:'
|
||||||
|
type: Escreva e prima enter para pesquisar.
|
||||||
|
types: Tipos
|
||||||
|
video: Vídeos
|
||||||
|
writeToSearch: Escreva aqui para pesquisar
|
||||||
settings:
|
settings:
|
||||||
admin: Admin
|
admin: Admin
|
||||||
administrator: Administrador
|
administrator: Administrador
|
||||||
allowCommands: Executar comandos
|
allowCommands: Executar comandos
|
||||||
allowEdit: Editar, renomear e eliminar ficheiros ou pastas
|
allowEdit: Editar, renomear e eliminar ficheiros ou pastas
|
||||||
allowNew: Criar novos ficheiros e pastas
|
allowNew: Criar novos ficheiros e pastas
|
||||||
|
allowPublish: Publicar novas páginas e conteúdos
|
||||||
avoidChanges: "(deixe em branco para manter)"
|
avoidChanges: "(deixe em branco para manter)"
|
||||||
changePassword: Alterar Password
|
changePassword: Alterar Password
|
||||||
commands: Comandos
|
commands: Comandos
|
||||||
commandsHelp: >
|
commandsHelp: >
|
||||||
Pode definir um conjunto de comandos a executar em determiandos eventos. Deve
|
Pode definir um conjunto de comandos a executar em determiandos eventos.
|
||||||
escrever um comando por linha. Se o evento estiver relacionado com ficheiros,
|
Deve escrever um comando por linha. Se o evento estiver relacionado com ficheiros,
|
||||||
como antes e depois de guardar, irá existir uma variável de ambiente denominada
|
como antes e depois de guardar, irá existir uma variável de ambiente denominada
|
||||||
"file" com o caminho do ficheiro.
|
"file" com o caminho do ficheiro.
|
||||||
commandsUpdated: Comandos atualizados!
|
commandsUpdated: Comandos atualizados!
|
||||||
|
@ -119,32 +143,32 @@ settings:
|
||||||
passwordUpdated: Palavra-passe atualizada!
|
passwordUpdated: Palavra-passe atualizada!
|
||||||
permissions: Permissões
|
permissions: Permissões
|
||||||
permissionsHelp: >
|
permissionsHelp: >
|
||||||
Pode definir o utilizador como administrador ou escolher as permissões manualmente.
|
Pode definir o utilizador como administrador ou escolher as permissões
|
||||||
Se selecionar a opção "Administrador", todas as outras opções serão automaticamente
|
manualmente. Se selecionar a opção "Administrador", todas as outras opções serão
|
||||||
selecionadas. A gestão dos utilizadores é um privilégio restringido aos administradores.
|
automaticamente selecionadas. A gestão dos utilizadores é um privilégio restringido
|
||||||
pluginsUpdated: Plugins atualizados!
|
aos administradores.
|
||||||
profileSettings: Configurações do Utilizador
|
profileSettings: Configurações do Utilizador
|
||||||
ruleExample1: >
|
ruleExample1: >
|
||||||
previne o acesso a qualquer "dotfile" (como .git, .gitignore) em qualquer pasta
|
previne o acesso a qualquer "dotfile" (como .git, .gitignore) em
|
||||||
|
qualquer pasta
|
||||||
ruleExample2: bloqueia o acesso ao ficheiro chamado Caddyfile.
|
ruleExample2: bloqueia o acesso ao ficheiro chamado Caddyfile.
|
||||||
rules: Regras
|
rules: Regras
|
||||||
rulesHelp1: >
|
rulesHelp1: >
|
||||||
Aqui pode definir um conjunto de regras para permitir ou bloquear o acesso
|
Aqui pode definir um conjunto de regras para permitir ou bloquear o
|
||||||
do utilizador a determinados ficheiros ou pastas. Os ficheiros bloqueados não
|
acesso do utilizador a determinados ficheiros ou pastas. Os ficheiros bloqueados
|
||||||
irão aparecer durante a navegação. Suportamos expressões regulares e os caminhos
|
não irão aparecer durante a navegação. Suportamos expressões regulares e os caminhos
|
||||||
dos ficheiros devem ser relativos à base do utilizador.
|
dos ficheiros devem ser relativos à base do utilizador.
|
||||||
rulesHelp2: >
|
rulesHelp2: >
|
||||||
Cada regra deve ser colocada numa linha diferente e deve começar com as
|
Cada regra deve ser colocada numa linha diferente e deve começar com
|
||||||
palavras {0} (permite) ou {1} (bloqueia). Deve escrever, logo de seguida, {2},
|
as palavras {0} (permite) ou {1} (bloqueia). Deve escrever, logo de seguida, {2},
|
||||||
caso queira utilizar uma expressão regular. Depois, escreva o caminho do ficheiro/pasta
|
caso queira utilizar uma expressão regular. Depois, escreva o caminho do ficheiro/pasta
|
||||||
ou a expressão regular.
|
ou a expressão regular.
|
||||||
scope: Base
|
scope: Base
|
||||||
settingsUpdated: Configurações atualizadas!
|
settingsUpdated: Configurações atualizadas!
|
||||||
user: Utilizador
|
user: Utilizador
|
||||||
userCommands: Comandos
|
userCommands: Comandos
|
||||||
userCommandsHelp:
|
userCommandsHelp: 'Uma lista, separada com espaços, de comandos disponíveis para
|
||||||
'Uma lista, separada com espaços, de comandos disponíveis para este
|
este utilizados. Exemplo:'
|
||||||
utilizados. Exemplo:'
|
|
||||||
userCreated: Utilizador criado!
|
userCreated: Utilizador criado!
|
||||||
userDeleted: Utilizador eliminado!
|
userDeleted: Utilizador eliminado!
|
||||||
userManagement: Gestão de Utilizadores
|
userManagement: Gestão de Utilizadores
|
||||||
|
@ -153,26 +177,12 @@ settings:
|
||||||
userUpdated: Utilizador atualizado!
|
userUpdated: Utilizador atualizado!
|
||||||
sidebar:
|
sidebar:
|
||||||
help: Ajuda
|
help: Ajuda
|
||||||
|
hugoNew: Hugo New
|
||||||
logout: Sair
|
logout: Sair
|
||||||
myFiles: Ficheiros
|
myFiles: Ficheiros
|
||||||
newFile: Novo ficheiro
|
newFile: Novo ficheiro
|
||||||
newFolder: Nova pasta
|
newFolder: Nova pasta
|
||||||
|
preview: Pré-visualizar
|
||||||
servedWith: Servido com
|
servedWith: Servido com
|
||||||
settings: Configurações
|
settings: Configurações
|
||||||
search:
|
siteSettings: Configurações do Site
|
||||||
images: Imagens
|
|
||||||
music: Música
|
|
||||||
pdf: PDF
|
|
||||||
writeToSearch: Escreva aqui para pesquisar
|
|
||||||
searchOrCommand: Pesquise ou execute um comando...
|
|
||||||
searchOrSupportedCommand: 'Pesquise ou utilize um dos seus comandos:'
|
|
||||||
search: Pesquise...
|
|
||||||
type: Escreva e prima enter para pesquisar.
|
|
||||||
types: Tipos
|
|
||||||
video: Vídeos
|
|
||||||
pressToSearch: Prima enter para pesquisar.
|
|
||||||
pressToExecute: Prima enter para executar.
|
|
||||||
languages:
|
|
||||||
en: Inglês
|
|
||||||
pt: Português
|
|
||||||
zhCN: Chinês (Simplificado)
|
|
||||||
|
|
|
@ -120,7 +120,6 @@ settings:
|
||||||
permissionsHelp: >
|
permissionsHelp: >
|
||||||
'您可以将该用户设置为管理员 或单独选择各项权限. 如果选择 "管理员(Administrator)" ,
|
'您可以将该用户设置为管理员 或单独选择各项权限. 如果选择 "管理员(Administrator)" ,
|
||||||
将自动检查所有其他选项, 并且该用户可以管理其他用户.'
|
将自动检查所有其他选项, 并且该用户可以管理其他用户.'
|
||||||
pluginsUpdated: 插件设置更新!
|
|
||||||
profileSettings: 配置文件设置
|
profileSettings: 配置文件设置
|
||||||
ruleExample1: >
|
ruleExample1: >
|
||||||
'阻止用户访问每个文件夹下任何以 . 开头的文件(隐藏文件, 例如: .git, .gitignore).'
|
'阻止用户访问每个文件夹下任何以 . 开头的文件(隐藏文件, 例如: .git, .gitignore).'
|
||||||
|
@ -152,13 +151,18 @@ sidebar:
|
||||||
servedWith: 服务提供
|
servedWith: 服务提供
|
||||||
settings: 设置
|
settings: 设置
|
||||||
search:
|
search:
|
||||||
writeToSearch: 请输入要搜索的内容
|
images: 图像
|
||||||
|
music: 音乐
|
||||||
|
pdf: PDF
|
||||||
|
pressToExecute: 按 Enter 键(回车)执行.
|
||||||
|
pressToSearch: 按 Enter 键(回车)进行搜索.
|
||||||
|
search: 搜索...
|
||||||
searchOrCommand: 搜索或者执行命令(Linux 代码)...
|
searchOrCommand: 搜索或者执行命令(Linux 代码)...
|
||||||
searchOrSupportedCommand: '搜索或使用您支持使用的命令(一次只能执行一个命令):'
|
searchOrSupportedCommand: '搜索或使用您支持使用的命令(一次只能执行一个命令):'
|
||||||
search: 搜索...
|
|
||||||
type: 键入并按 Enter 键(回车)进行搜索.
|
type: 键入并按 Enter 键(回车)进行搜索.
|
||||||
pressToSearch: 按 Enter 键(回车)进行搜索.
|
types: 类型
|
||||||
pressToExecute: 按 Enter 键(回车)执行.
|
video: 视频
|
||||||
|
writeToSearch: 请输入要搜索的内容
|
||||||
languages:
|
languages:
|
||||||
en: English
|
en: English
|
||||||
pt: Portuguese
|
pt: Portuguese
|
||||||
|
|
|
@ -8,13 +8,14 @@ Vue.use(Vuex)
|
||||||
const state = {
|
const state = {
|
||||||
user: {},
|
user: {},
|
||||||
req: {},
|
req: {},
|
||||||
plugins: window.plugins || [],
|
|
||||||
clipboard: {
|
clipboard: {
|
||||||
key: '',
|
key: '',
|
||||||
items: []
|
items: []
|
||||||
},
|
},
|
||||||
|
staticGen: document.querySelector('meta[name="staticgen"]').getAttribute('content'),
|
||||||
baseURL: document.querySelector('meta[name="base"]').getAttribute('content'),
|
baseURL: document.querySelector('meta[name="base"]').getAttribute('content'),
|
||||||
jwt: '',
|
jwt: '',
|
||||||
|
schedule: '',
|
||||||
loading: false,
|
loading: false,
|
||||||
reload: false,
|
reload: false,
|
||||||
selected: [],
|
selected: [],
|
||||||
|
|
|
@ -32,6 +32,9 @@ const mutations = {
|
||||||
setJWT: (state, value) => (state.jwt = value),
|
setJWT: (state, value) => (state.jwt = value),
|
||||||
multiple: (state, value) => (state.multiple = value),
|
multiple: (state, value) => (state.multiple = value),
|
||||||
addSelected: (state, value) => (state.selected.push(value)),
|
addSelected: (state, value) => (state.selected.push(value)),
|
||||||
|
addPlugin: (state, value) => {
|
||||||
|
state.plugins.push(value)
|
||||||
|
},
|
||||||
removeSelected: (state, value) => {
|
removeSelected: (state, value) => {
|
||||||
let i = state.selected.indexOf(value)
|
let i = state.selected.indexOf(value)
|
||||||
if (i === -1) return
|
if (i === -1) return
|
||||||
|
@ -53,6 +56,9 @@ const mutations = {
|
||||||
resetClipboard: (state) => {
|
resetClipboard: (state) => {
|
||||||
state.clipboard.key = ''
|
state.clipboard.key = ''
|
||||||
state.clipboard.items = []
|
state.clipboard.items = []
|
||||||
|
},
|
||||||
|
setSchedule: (state, value) => {
|
||||||
|
state.schedule = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,13 +85,18 @@ export function post (url, content = '', overwrite = false) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function put (url, content = '') {
|
export function put (url, content = '', publish = false, date = '') {
|
||||||
url = removePrefix(url)
|
url = removePrefix(url)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let request = new window.XMLHttpRequest()
|
let request = new window.XMLHttpRequest()
|
||||||
request.open('PUT', `${store.state.baseURL}/api/resource${url}`, true)
|
request.open('PUT', `${store.state.baseURL}/api/resource${url}`, true)
|
||||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||||
|
request.setRequestHeader('Publish', publish)
|
||||||
|
|
||||||
|
if (date !== '') {
|
||||||
|
request.setRequestHeader('Schedule', date)
|
||||||
|
}
|
||||||
|
|
||||||
request.onload = () => {
|
request.onload = () => {
|
||||||
if (request.status === 200) {
|
if (request.status === 200) {
|
||||||
|
|
|
@ -15,17 +15,15 @@
|
||||||
|
|
||||||
<h1>{{ $t('settings.globalSettings') }}</h1>
|
<h1>{{ $t('settings.globalSettings') }}</h1>
|
||||||
|
|
||||||
<form @submit="savePlugin" v-if="plugins.length > 0">
|
<form @submit="saveStaticGen" v-if="$store.state.staticGen.length > 0">
|
||||||
<template v-for="plugin in plugins">
|
<h2>{{ capitalize($store.state.staticGen) }}</h2>
|
||||||
<h2>{{ capitalize(plugin.name) }}</h2>
|
|
||||||
|
|
||||||
<p v-for="field in plugin.fields" :key="field.variable">
|
<p v-for="field in staticGen" :key="field.variable">
|
||||||
<label v-if="field.type !== 'checkbox'">{{ field.name }}</label>
|
<label v-if="field.type !== 'checkbox'">{{ field.name }}</label>
|
||||||
<input v-if="field.type === 'text'" type="text" v-model.trim="field.value">
|
<input v-if="field.type === 'text'" type="text" v-model.trim="field.value">
|
||||||
<input v-else-if="field.type === 'checkbox'" type="checkbox" v-model.trim="field.value">
|
<input v-else-if="field.type === 'checkbox'" type="checkbox" v-model.trim="field.value">
|
||||||
<template v-if="field.type === 'checkbox'">{{ capitalize(field.name, 'caps') }}</template>
|
<template v-if="field.type === 'checkbox'">{{ capitalize(field.name, 'caps') }}</template>
|
||||||
</p>
|
</p>
|
||||||
</template>
|
|
||||||
|
|
||||||
<p><input type="submit" value="Save"></p>
|
<p><input type="submit" value="Save"></p>
|
||||||
</form>
|
</form>
|
||||||
|
@ -55,7 +53,7 @@ export default {
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
commands: [],
|
commands: [],
|
||||||
plugins: []
|
staticGen: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -64,8 +62,8 @@ export default {
|
||||||
created () {
|
created () {
|
||||||
getSettings()
|
getSettings()
|
||||||
.then(settings => {
|
.then(settings => {
|
||||||
for (let key in settings.plugins) {
|
if (this.$store.state.staticGen.length > 0) {
|
||||||
this.plugins.push(this.parsePlugin(key, settings.plugins[key]))
|
this.parseStaticGen(settings.staticGen)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let key in settings.commands) {
|
for (let key in settings.commands) {
|
||||||
|
@ -108,40 +106,29 @@ export default {
|
||||||
.then(() => { this.showSuccess(this.$t('settings.commandsUpdated')) })
|
.then(() => { this.showSuccess(this.$t('settings.commandsUpdated')) })
|
||||||
.catch(error => { this.showError(error) })
|
.catch(error => { this.showError(error) })
|
||||||
},
|
},
|
||||||
savePlugin (event) {
|
saveStaticGen (event) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
let plugins = {}
|
let staticGen = {}
|
||||||
|
|
||||||
for (let plugin of this.plugins) {
|
for (let field of this.staticGen) {
|
||||||
let p = {}
|
staticGen[field.variable] = field.value
|
||||||
|
|
||||||
for (let field of plugin.fields) {
|
if (field.original === 'array') {
|
||||||
p[field.variable] = field.value
|
let val = field.value.split(' ')
|
||||||
|
if (val[0] === '') {
|
||||||
if (field.original === 'array') {
|
val.shift()
|
||||||
let val = field.value.split(' ')
|
|
||||||
if (val[0] === '') {
|
|
||||||
val.shift()
|
|
||||||
}
|
|
||||||
|
|
||||||
p[field.variable] = val
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
plugins[plugin.name] = p
|
staticGen[field.variable] = val
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSettings(plugins, 'plugins')
|
updateSettings(staticGen, 'staticGen')
|
||||||
.then(() => { this.showSuccess(this.$t('settings.pluginsUpdated')) })
|
.then(() => { this.showSuccess(this.$t('settings.settingsUpdated')) })
|
||||||
.catch(error => { this.showError(error) })
|
.catch(error => { this.showError(error) })
|
||||||
},
|
},
|
||||||
parsePlugin (name, plugin) {
|
parseStaticGen (staticgen) {
|
||||||
let obj = {
|
for (let option of staticgen) {
|
||||||
name: name,
|
|
||||||
fields: []
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let option of plugin) {
|
|
||||||
let value = option.value
|
let value = option.value
|
||||||
|
|
||||||
let field = {
|
let field = {
|
||||||
|
@ -156,7 +143,7 @@ export default {
|
||||||
field.original = 'array'
|
field.original = 'array'
|
||||||
field.value = value.join(' ')
|
field.value = value.join(' ')
|
||||||
|
|
||||||
obj.fields.push(field)
|
this.staticGen.push(field)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,10 +154,8 @@ export default {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.fields.push(field)
|
this.staticGen.push(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,9 +28,7 @@
|
||||||
<p><input type="checkbox" :disabled="admin" v-model="allowNew"> {{ $t('settings.allowNew') }}</p>
|
<p><input type="checkbox" :disabled="admin" v-model="allowNew"> {{ $t('settings.allowNew') }}</p>
|
||||||
<p><input type="checkbox" :disabled="admin" v-model="allowEdit"> {{ $t('settings.allowEdit') }}</p>
|
<p><input type="checkbox" :disabled="admin" v-model="allowEdit"> {{ $t('settings.allowEdit') }}</p>
|
||||||
<p><input type="checkbox" :disabled="admin" v-model="allowCommands"> {{ $t('settings.allowCommands') }}</p>
|
<p><input type="checkbox" :disabled="admin" v-model="allowCommands"> {{ $t('settings.allowCommands') }}</p>
|
||||||
<p v-for="(value, key) in permissions" :key="key">
|
<p v-show="$store.state.staticGen.length"><input type="checkbox" :disabled="admin" v-model="allowPublish"> {{ $t('settings.allowPublish') }}</p>
|
||||||
<input type="checkbox" :disabled="admin" v-model="permissions[key]"> {{ capitalize(key) }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>{{ $t('settings.userCommands') }}</h3>
|
<h3>{{ $t('settings.userCommands') }}</h3>
|
||||||
<p class="small">{{ $t('settings.userCommandsHelp') }} <i>git svn hg</i>.</p>
|
<p class="small">{{ $t('settings.userCommandsHelp') }} <i>git svn hg</i>.</p>
|
||||||
|
@ -94,6 +92,7 @@ export default {
|
||||||
allowNew: false,
|
allowNew: false,
|
||||||
allowEdit: false,
|
allowEdit: false,
|
||||||
allowCommands: false,
|
allowCommands: false,
|
||||||
|
allowPublish: false,
|
||||||
permissions: {},
|
permissions: {},
|
||||||
password: '',
|
password: '',
|
||||||
username: '',
|
username: '',
|
||||||
|
@ -120,6 +119,7 @@ export default {
|
||||||
this.allowCommands = true
|
this.allowCommands = true
|
||||||
this.allowEdit = true
|
this.allowEdit = true
|
||||||
this.allowNew = true
|
this.allowNew = true
|
||||||
|
this.allowPublish = true
|
||||||
for (let key in this.permissions) {
|
for (let key in this.permissions) {
|
||||||
this.permissions[key] = true
|
this.permissions[key] = true
|
||||||
}
|
}
|
||||||
|
@ -140,6 +140,7 @@ export default {
|
||||||
this.allowCommands = user.allowCommands
|
this.allowCommands = user.allowCommands
|
||||||
this.allowNew = user.allowNew
|
this.allowNew = user.allowNew
|
||||||
this.allowEdit = user.allowEdit
|
this.allowEdit = user.allowEdit
|
||||||
|
this.allowPublish = user.allowPublish
|
||||||
this.filesystem = user.filesystem
|
this.filesystem = user.filesystem
|
||||||
this.username = user.username
|
this.username = user.username
|
||||||
this.commands = user.commands.join(' ')
|
this.commands = user.commands.join(' ')
|
||||||
|
@ -183,6 +184,7 @@ export default {
|
||||||
this.admin = false
|
this.admin = false
|
||||||
this.allowNew = false
|
this.allowNew = false
|
||||||
this.allowEdit = false
|
this.allowEdit = false
|
||||||
|
this.allowPublish = false
|
||||||
this.permissins = {}
|
this.permissins = {}
|
||||||
this.allowCommands = false
|
this.allowCommands = false
|
||||||
this.password = ''
|
this.password = ''
|
||||||
|
@ -241,6 +243,7 @@ export default {
|
||||||
allowCommands: this.allowCommands,
|
allowCommands: this.allowCommands,
|
||||||
allowNew: this.allowNew,
|
allowNew: this.allowNew,
|
||||||
allowEdit: this.allowEdit,
|
allowEdit: this.allowEdit,
|
||||||
|
allowPublish: this.allowPublish,
|
||||||
permissions: this.permissions,
|
permissions: this.permissions,
|
||||||
css: this.css,
|
css: this.css,
|
||||||
locale: this.locale,
|
locale: this.locale,
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hacdias/filemanager"
|
"github.com/hacdias/filemanager"
|
||||||
"github.com/hacdias/filemanager/plugins"
|
|
||||||
"github.com/hacdias/fileutils"
|
"github.com/hacdias/fileutils"
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||||
|
@ -112,7 +111,6 @@ func parse(c *caddy.Controller) ([]*filemanager.FileManager, error) {
|
||||||
AllowCommands: true,
|
AllowCommands: true,
|
||||||
AllowEdit: true,
|
AllowEdit: true,
|
||||||
AllowNew: true,
|
AllowNew: true,
|
||||||
Permissions: map[string]bool{},
|
|
||||||
Commands: []string{"git", "svn", "hg"},
|
Commands: []string{"git", "svn", "hg"},
|
||||||
Rules: []*filemanager.Rule{{
|
Rules: []*filemanager.Rule{{
|
||||||
Regex: true,
|
Regex: true,
|
||||||
|
@ -128,20 +126,15 @@ func parse(c *caddy.Controller) ([]*filemanager.FileManager, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the default settings for Hugo.
|
// Initialize the default settings for Hugo.
|
||||||
hugo := &plugins.Hugo{
|
hugo := &filemanager.Hugo{
|
||||||
Root: directory,
|
Root: directory,
|
||||||
Public: filepath.Join(directory, "public"),
|
Public: filepath.Join(directory, "public"),
|
||||||
Args: []string{},
|
Args: []string{},
|
||||||
CleanPublic: true,
|
CleanPublic: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find the Hugo executable path.
|
|
||||||
if err = hugo.Find(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attaches Hugo plugin to this file manager instance.
|
// Attaches Hugo plugin to this file manager instance.
|
||||||
err = m.ActivatePlugin("hugo", hugo)
|
err = m.EnableStaticGen(hugo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,6 @@ import (
|
||||||
|
|
||||||
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
|
||||||
"github.com/hacdias/filemanager/plugins"
|
|
||||||
|
|
||||||
"github.com/hacdias/filemanager"
|
"github.com/hacdias/filemanager"
|
||||||
"github.com/hacdias/fileutils"
|
"github.com/hacdias/fileutils"
|
||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
|
@ -27,7 +25,7 @@ var (
|
||||||
scope string
|
scope string
|
||||||
commands string
|
commands string
|
||||||
logfile string
|
logfile string
|
||||||
plugin string
|
staticgen string
|
||||||
locale string
|
locale string
|
||||||
port int
|
port int
|
||||||
noAuth bool
|
noAuth bool
|
||||||
|
@ -51,7 +49,7 @@ func init() {
|
||||||
flag.BoolVar(&allowNew, "allow-new", true, "Default allow new option for new users")
|
flag.BoolVar(&allowNew, "allow-new", true, "Default allow new option for new users")
|
||||||
flag.BoolVar(&noAuth, "no-auth", false, "Disables authentication")
|
flag.BoolVar(&noAuth, "no-auth", false, "Disables authentication")
|
||||||
flag.StringVar(&locale, "locale", "en", "Default locale for new users")
|
flag.StringVar(&locale, "locale", "en", "Default locale for new users")
|
||||||
flag.StringVar(&plugin, "plugin", "", "Plugin you want to enable")
|
flag.StringVar(&staticgen, "staticgen", "", "Static Generator you want to enable")
|
||||||
flag.BoolVarP(&showVer, "version", "v", false, "Show version")
|
flag.BoolVarP(&showVer, "version", "v", false, "Show version")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +63,7 @@ func setupViper() {
|
||||||
viper.SetDefault("AllowCommmands", true)
|
viper.SetDefault("AllowCommmands", true)
|
||||||
viper.SetDefault("AllowEdit", true)
|
viper.SetDefault("AllowEdit", true)
|
||||||
viper.SetDefault("AllowNew", true)
|
viper.SetDefault("AllowNew", true)
|
||||||
viper.SetDefault("Plugin", "")
|
viper.SetDefault("StaticGen", "")
|
||||||
viper.SetDefault("Locale", "en")
|
viper.SetDefault("Locale", "en")
|
||||||
viper.SetDefault("NoAuth", false)
|
viper.SetDefault("NoAuth", false)
|
||||||
|
|
||||||
|
@ -79,7 +77,7 @@ func setupViper() {
|
||||||
viper.BindPFlag("AllowEdit", flag.Lookup("allow-edit"))
|
viper.BindPFlag("AllowEdit", flag.Lookup("allow-edit"))
|
||||||
viper.BindPFlag("AlowNew", flag.Lookup("allow-new"))
|
viper.BindPFlag("AlowNew", flag.Lookup("allow-new"))
|
||||||
viper.BindPFlag("Locale", flag.Lookup("locale"))
|
viper.BindPFlag("Locale", flag.Lookup("locale"))
|
||||||
viper.BindPFlag("Plugin", flag.Lookup("plugin"))
|
viper.BindPFlag("StaticGen", flag.Lookup("staticgen"))
|
||||||
viper.BindPFlag("NoAuth", flag.Lookup("no-auth"))
|
viper.BindPFlag("NoAuth", flag.Lookup("no-auth"))
|
||||||
|
|
||||||
viper.SetConfigName("filemanager")
|
viper.SetConfigName("filemanager")
|
||||||
|
@ -166,21 +164,16 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if viper.GetString("Plugin") == "hugo" {
|
if viper.GetString("StaticGen") == "hugo" {
|
||||||
// Initialize the default settings for Hugo.
|
// Initialize the default settings for Hugo.
|
||||||
hugo := &plugins.Hugo{
|
hugo := &filemanager.Hugo{
|
||||||
Root: viper.GetString("Scope"),
|
Root: viper.GetString("Scope"),
|
||||||
Public: filepath.Join(viper.GetString("Scope"), "public"),
|
Public: filepath.Join(viper.GetString("Scope"), "public"),
|
||||||
Args: []string{},
|
Args: []string{},
|
||||||
CleanPublic: true,
|
CleanPublic: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find the Hugo executable path.
|
if err = fm.EnableStaticGen(hugo); err != nil {
|
||||||
if err = hugo.Find(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = fm.ActivatePlugin("hugo", hugo); err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
180
filemanager.go
180
filemanager.go
|
@ -78,7 +78,6 @@ var (
|
||||||
errEmptyScope = errors.New("scope is empty")
|
errEmptyScope = errors.New("scope is empty")
|
||||||
errWrongDataType = errors.New("wrong data type")
|
errWrongDataType = errors.New("wrong data type")
|
||||||
errInvalidUpdateField = errors.New("invalid field to update")
|
errInvalidUpdateField = errors.New("invalid field to update")
|
||||||
plugins = map[string]Plugin{}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// FileManager is a file manager instance. It should be creating using the
|
// FileManager is a file manager instance. It should be creating using the
|
||||||
|
@ -107,6 +106,11 @@ type FileManager struct {
|
||||||
// there will only exist one user, called "admin".
|
// there will only exist one user, called "admin".
|
||||||
NoAuth bool
|
NoAuth bool
|
||||||
|
|
||||||
|
// staticgen is the name of the current static website generator.
|
||||||
|
staticgen string
|
||||||
|
// StaticGen is the static websit generator handler.
|
||||||
|
StaticGen StaticGen
|
||||||
|
|
||||||
// The Default User needed to build the New User page.
|
// The Default User needed to build the New User page.
|
||||||
DefaultUser *User
|
DefaultUser *User
|
||||||
|
|
||||||
|
@ -115,9 +119,15 @@ type FileManager struct {
|
||||||
|
|
||||||
// A map of events to a slice of commands.
|
// A map of events to a slice of commands.
|
||||||
Commands map[string][]string
|
Commands map[string][]string
|
||||||
|
}
|
||||||
|
|
||||||
// The options of the plugins that have been plugged into this instance.
|
type StaticGen interface {
|
||||||
Plugins map[string]interface{}
|
SettingsPath() string
|
||||||
|
|
||||||
|
Hook(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error)
|
||||||
|
Preview(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error)
|
||||||
|
Publish(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error)
|
||||||
|
Schedule(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command is a command function.
|
// Command is a command function.
|
||||||
|
@ -151,10 +161,10 @@ type User struct {
|
||||||
Locale string `json:"locale"`
|
Locale string `json:"locale"`
|
||||||
|
|
||||||
// These indicate if the user can perform certain actions.
|
// These indicate if the user can perform certain actions.
|
||||||
AllowNew bool `json:"allowNew"` // Create files and folders
|
AllowNew bool `json:"allowNew"` // Create files and folders
|
||||||
AllowEdit bool `json:"allowEdit"` // Edit/rename files
|
AllowEdit bool `json:"allowEdit"` // Edit/rename files
|
||||||
AllowCommands bool `json:"allowCommands"` // Execute commands
|
AllowCommands bool `json:"allowCommands"` // Execute commands
|
||||||
Permissions map[string]bool `json:"permissions"` // Permissions added by plugins
|
AllowPublish bool `json:"allowPublish"` // Publish content (to use with static gen)
|
||||||
|
|
||||||
// Commands is the list of commands the user can execute.
|
// Commands is the list of commands the user can execute.
|
||||||
Commands []string `json:"commands"`
|
Commands []string `json:"commands"`
|
||||||
|
@ -175,43 +185,18 @@ type Rule struct {
|
||||||
Regexp *Regexp `json:"regexp"`
|
Regexp *Regexp `json:"regexp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PluginHandler func(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error)
|
|
||||||
|
|
||||||
// Regexp is a regular expression wrapper around native regexp.
|
// Regexp is a regular expression wrapper around native regexp.
|
||||||
type Regexp struct {
|
type Regexp struct {
|
||||||
Raw string `json:"raw"`
|
Raw string `json:"raw"`
|
||||||
regexp *regexp.Regexp
|
regexp *regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
||||||
type Plugin struct {
|
|
||||||
JavaScript string
|
|
||||||
CommandEvents []string
|
|
||||||
Permissions []Permission
|
|
||||||
Options interface{}
|
|
||||||
Handlers map[string]PluginHandler `json:"-"`
|
|
||||||
BeforeAPI PluginHandler `json:"-"`
|
|
||||||
AfterAPI PluginHandler `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Permission struct {
|
|
||||||
Name string
|
|
||||||
Value bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterPlugin(name string, plugin Plugin) {
|
|
||||||
if _, ok := plugins[name]; ok {
|
|
||||||
panic(name + " plugin is already registred")
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins[name] = plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultUser is used on New, when no 'base' user is provided.
|
// DefaultUser is used on New, when no 'base' user is provided.
|
||||||
var DefaultUser = User{
|
var DefaultUser = User{
|
||||||
AllowCommands: true,
|
AllowCommands: true,
|
||||||
AllowEdit: true,
|
AllowEdit: true,
|
||||||
AllowNew: true,
|
AllowNew: true,
|
||||||
Permissions: map[string]bool{},
|
AllowPublish: true,
|
||||||
Commands: []string{},
|
Commands: []string{},
|
||||||
Rules: []*Rule{},
|
Rules: []*Rule{},
|
||||||
CSS: "",
|
CSS: "",
|
||||||
|
@ -228,9 +213,8 @@ func New(database string, base User) (*FileManager, error) {
|
||||||
// Creates a new File Manager instance with the Users
|
// Creates a new File Manager instance with the Users
|
||||||
// map and Assets box.
|
// map and Assets box.
|
||||||
m := &FileManager{
|
m := &FileManager{
|
||||||
Users: map[string]*User{},
|
Users: map[string]*User{},
|
||||||
Plugins: map[string]interface{}{},
|
assets: rice.MustFindBox("./assets/dist"),
|
||||||
assets: rice.MustFindBox("./assets/dist"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tries to open a database on the location provided. This
|
// Tries to open a database on the location provided. This
|
||||||
|
@ -264,8 +248,10 @@ func New(database string, base User) (*FileManager, error) {
|
||||||
err = db.Get("config", "commands", &m.Commands)
|
err = db.Get("config", "commands", &m.Commands)
|
||||||
if err != nil && err == storm.ErrNotFound {
|
if err != nil && err == storm.ErrNotFound {
|
||||||
m.Commands = map[string][]string{
|
m.Commands = map[string][]string{
|
||||||
"before_save": {},
|
"before_save": {},
|
||||||
"after_save": {},
|
"after_save": {},
|
||||||
|
"before_publish": {},
|
||||||
|
"after_publish": {},
|
||||||
}
|
}
|
||||||
err = db.Set("config", "commands", m.Commands)
|
err = db.Set("config", "commands", m.Commands)
|
||||||
}
|
}
|
||||||
|
@ -346,95 +332,7 @@ func (m *FileManager) SetBaseURL(url string) {
|
||||||
m.BaseURL = strings.TrimSuffix(url, "/")
|
m.BaseURL = strings.TrimSuffix(url, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActivatePlugin activates a plugin to a File Manager instance and
|
// ServeHTTP handles the request.
|
||||||
// loads its options from the database.
|
|
||||||
func (m *FileManager) ActivatePlugin(name string, options interface{}) error {
|
|
||||||
if reflect.TypeOf(options).Kind() != reflect.Ptr {
|
|
||||||
return errors.New("options should be a pointer to interface, not interface")
|
|
||||||
}
|
|
||||||
|
|
||||||
var plugin Plugin
|
|
||||||
|
|
||||||
if p, ok := plugins[name]; !ok {
|
|
||||||
plugin = p
|
|
||||||
return errors.New(name + " plugin is not registred")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := m.Plugins[name]; ok {
|
|
||||||
return errors.New(name + " plugin is already activated")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := m.db.Get("plugins", name, &plugin)
|
|
||||||
if err != nil && err == storm.ErrNotFound {
|
|
||||||
err = m.db.Set("plugin", name, plugin)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register the command event hooks.
|
|
||||||
for _, evt := range plugin.CommandEvents {
|
|
||||||
if _, ok := m.Commands[evt]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
m.Commands[evt] = []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = m.db.Set("config", "commands", m.Commands)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register the user permissions.
|
|
||||||
for _, perm := range plugin.Permissions {
|
|
||||||
err = m.registerPermission(perm.Name, perm.Value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.Plugins[name] = options
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// registerPermission registers a new user permission and adds it to every
|
|
||||||
// user with it default's 'value'. If the user is an admin, it will
|
|
||||||
// be true.
|
|
||||||
func (m *FileManager) registerPermission(name string, value bool) error {
|
|
||||||
if _, ok := m.DefaultUser.Permissions[name]; ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the default value for this permission on the default user.
|
|
||||||
m.DefaultUser.Permissions[name] = value
|
|
||||||
|
|
||||||
for _, u := range m.Users {
|
|
||||||
// Bypass the user if it is already defined.
|
|
||||||
if _, ok := u.Permissions[name]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Permissions == nil {
|
|
||||||
u.Permissions = m.DefaultUser.Permissions
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Admin {
|
|
||||||
u.Permissions[name] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
err := m.db.Save(u)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
|
|
||||||
// Compatible with http.Handler.
|
|
||||||
func (m *FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (m *FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
code, err := serveHTTP(&RequestContext{
|
code, err := serveHTTP(&RequestContext{
|
||||||
FileManager: m,
|
FileManager: m,
|
||||||
|
@ -458,6 +356,34 @@ func (m *FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *FileManager) EnableStaticGen(data StaticGen) error {
|
||||||
|
if reflect.TypeOf(data).Kind() != reflect.Ptr {
|
||||||
|
return errors.New("data should be a pointer to interface, not interface")
|
||||||
|
}
|
||||||
|
|
||||||
|
if h, ok := data.(*Hugo); ok {
|
||||||
|
return m.enableHugo(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("unknown static website generator")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FileManager) enableHugo(hugo *Hugo) error {
|
||||||
|
if err := hugo.find(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.staticgen = "hugo"
|
||||||
|
m.StaticGen = hugo
|
||||||
|
|
||||||
|
err := m.db.Get("staticgen", "hugo", hugo)
|
||||||
|
if err != nil && err == storm.ErrNotFound {
|
||||||
|
err = m.db.Set("staticgen", "hugo", *hugo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Allowed checks if the user has permission to access a directory/file.
|
// Allowed checks if the user has permission to access a directory/file.
|
||||||
func (u User) Allowed(url string) bool {
|
func (u User) Allowed(url string) bool {
|
||||||
var rule *Rule
|
var rule *Rule
|
||||||
|
|
62
http.go
62
http.go
|
@ -58,28 +58,11 @@ func serveHTTP(c *RequestContext, w http.ResponseWriter, r *http.Request) (int,
|
||||||
return apiHandler(c, w, r)
|
return apiHandler(c, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if any plugin has an handler for this URL.
|
// If it is a request to the preview and a static website generator is
|
||||||
for p := range c.Plugins {
|
// active, build the preview.
|
||||||
var h PluginHandler
|
if strings.HasPrefix(r.URL.Path, "/preview") && c.StaticGen != nil {
|
||||||
|
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/preview")
|
||||||
for path, handler := range plugins[p].Handlers {
|
return c.StaticGen.Preview(c, w, r)
|
||||||
if strings.HasPrefix(r.URL.Path, path) {
|
|
||||||
h = handler
|
|
||||||
r.URL.Path = strings.TrimPrefix(r.URL.Path, path)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if h == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
valid, _ := validateAuth(c, r)
|
|
||||||
if !valid {
|
|
||||||
return http.StatusForbidden, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return h(c, w, r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any other request should show the index.html file.
|
// Any other request should show the index.html file.
|
||||||
|
@ -131,12 +114,15 @@ func apiHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int,
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for p := range c.Plugins {
|
if c.StaticGen != nil {
|
||||||
if plugins[p].BeforeAPI == nil {
|
// If we are using the 'magic url' for the settings,
|
||||||
continue
|
// we should redirect the request for the acutual path.
|
||||||
|
if r.URL.Path == "/settings" {
|
||||||
|
r.URL.Path = c.StaticGen.SettingsPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
code, err := plugins[p].BeforeAPI(c, w, r)
|
// Executes the Static website generator hook.
|
||||||
|
code, err := c.StaticGen.Hook(c, w, r)
|
||||||
if code != 0 || err != nil {
|
if code != 0 || err != nil {
|
||||||
return code, err
|
return code, err
|
||||||
}
|
}
|
||||||
|
@ -172,21 +158,6 @@ func apiHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int,
|
||||||
code = http.StatusNotFound
|
code = http.StatusNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
if code >= 300 || err != nil {
|
|
||||||
return code, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for p := range c.Plugins {
|
|
||||||
if plugins[p].AfterAPI == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
code, err := plugins[p].AfterAPI(c, w, r)
|
|
||||||
if code != 0 || err != nil {
|
|
||||||
return code, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return code, err
|
return code, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,14 +198,9 @@ func renderFile(w http.ResponseWriter, file string, contentType string, c *Reque
|
||||||
tpl := template.Must(template.New("file").Parse(file))
|
tpl := template.Must(template.New("file").Parse(file))
|
||||||
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
|
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
|
||||||
|
|
||||||
var javascript = ""
|
|
||||||
for name := range c.Plugins {
|
|
||||||
javascript += plugins[name].JavaScript + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
err := tpl.Execute(w, map[string]interface{}{
|
err := tpl.Execute(w, map[string]interface{}{
|
||||||
"BaseURL": c.RootURL(),
|
"BaseURL": c.RootURL(),
|
||||||
"JavaScript": template.JS(javascript),
|
"StaticGen": c.staticgen,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
|
|
258
plugins/hugo.go
258
plugins/hugo.go
|
@ -1,258 +0,0 @@
|
||||||
package plugins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hacdias/filemanager"
|
|
||||||
"github.com/hacdias/varutils"
|
|
||||||
"github.com/robfig/cron"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
filemanager.RegisterPlugin("hugo", filemanager.Plugin{
|
|
||||||
JavaScript: hugoJavaScript,
|
|
||||||
CommandEvents: []string{"before_publish", "after_publish"},
|
|
||||||
BeforeAPI: beforeAPI,
|
|
||||||
Handlers: map[string]filemanager.PluginHandler{
|
|
||||||
"/preview": previewHandler,
|
|
||||||
},
|
|
||||||
Permissions: []filemanager.Permission{
|
|
||||||
{
|
|
||||||
Name: "allowPublish",
|
|
||||||
Value: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrHugoNotFound = errors.New("It seems that tou don't have 'hugo' on your PATH")
|
|
||||||
ErrUnsupportedFileType = errors.New("The type of the provided file isn't supported for this action")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Hugo is a hugo (https://gohugo.io) plugin.
|
|
||||||
type Hugo struct {
|
|
||||||
// Website root
|
|
||||||
Root string `name:"Website Root"`
|
|
||||||
// Public folder
|
|
||||||
Public string `name:"Public Directory"`
|
|
||||||
// Hugo executable path
|
|
||||||
Exe string `name:"Hugo Executable"`
|
|
||||||
// Hugo arguments
|
|
||||||
Args []string `name:"Hugo Arguments"`
|
|
||||||
// Indicates if we should clean public before a new publish.
|
|
||||||
CleanPublic bool `name:"Clean Public"`
|
|
||||||
// previewPath is the temporary path for a preview
|
|
||||||
previewPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find finds the hugo executable in the path.
|
|
||||||
func (h *Hugo) Find() error {
|
|
||||||
var err error
|
|
||||||
if h.Exe, err = exec.LookPath("hugo"); err != nil {
|
|
||||||
return ErrHugoNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// run runs Hugo with the define arguments.
|
|
||||||
func (h Hugo) run(force bool) {
|
|
||||||
// If the CleanPublic option is enabled, clean it.
|
|
||||||
if h.CleanPublic {
|
|
||||||
os.RemoveAll(h.Public)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent running if watching is enabled
|
|
||||||
if b, pos := varutils.StringInSlice("--watch", h.Args); b && !force {
|
|
||||||
if len(h.Args) > pos && h.Args[pos+1] != "false" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(h.Args) == pos+1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := Run(h.Exe, h.Args, h.Root); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// schedule schedules a post to be published later.
|
|
||||||
func (h Hugo) schedule(c *filemanager.RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
||||||
t, err := time.Parse("2006-01-02T15:04", r.Header.Get("Schedule"))
|
|
||||||
path := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
|
||||||
path = filepath.Clean(path)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
scheduler := cron.New()
|
|
||||||
scheduler.AddFunc(t.Format("05 04 15 02 01 *"), func() {
|
|
||||||
if err := h.undraft(path); err != nil {
|
|
||||||
log.Printf(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.run(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
scheduler.Start()
|
|
||||||
return http.StatusOK, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Hugo) undraft(file string) error {
|
|
||||||
args := []string{"undraft", file}
|
|
||||||
if err := Run(h.Exe, args, h.Root); err != nil && !strings.Contains(err.Error(), "not a Draft") {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func beforeAPI(c *filemanager.RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
||||||
o := c.Plugins["hugo"].(*Hugo)
|
|
||||||
|
|
||||||
// If we are using the 'magic url' for the settings, we should redirect the
|
|
||||||
// request for the acutual path.
|
|
||||||
if r.URL.Path == "/settings/" || r.URL.Path == "/settings" {
|
|
||||||
var frontmatter string
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if _, err = os.Stat(filepath.Join(o.Root, "config.yaml")); err == nil {
|
|
||||||
frontmatter = "yaml"
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = os.Stat(filepath.Join(o.Root, "config.json")); err == nil {
|
|
||||||
frontmatter = "json"
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = os.Stat(filepath.Join(o.Root, "config.toml")); err == nil {
|
|
||||||
frontmatter = "toml"
|
|
||||||
}
|
|
||||||
|
|
||||||
r.URL.Path = "/config." + frontmatter
|
|
||||||
}
|
|
||||||
|
|
||||||
// From here on, we only care about 'hugo' router so we can bypass
|
|
||||||
// the others.
|
|
||||||
if c.Router != "hugo" {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are not using HTTP Post, we shall return Method Not Allowed
|
|
||||||
// since we are only working with this method.
|
|
||||||
if r.Method != http.MethodPost {
|
|
||||||
return http.StatusMethodNotAllowed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are creating a file built from an archetype.
|
|
||||||
if r.Header.Get("Archetype") != "" {
|
|
||||||
if !c.User.AllowNew {
|
|
||||||
return http.StatusForbidden, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
|
||||||
archetype := r.Header.Get("archetype")
|
|
||||||
|
|
||||||
ext := filepath.Ext(filename)
|
|
||||||
|
|
||||||
// If the request isn't for a markdown file, we can't
|
|
||||||
// handle it.
|
|
||||||
if ext != ".markdown" && ext != ".md" {
|
|
||||||
return http.StatusBadRequest, ErrUnsupportedFileType
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tries to create a new file based on this archetype.
|
|
||||||
args := []string{"new", filename, "--kind", archetype}
|
|
||||||
if err := Run(o.Exe, args, o.Root); err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writes the location of the new file to the Header.
|
|
||||||
w.Header().Set("Location", "/files/content/"+filename)
|
|
||||||
return http.StatusCreated, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are trying to regenerate the website.
|
|
||||||
if r.Header.Get("Regenerate") == "true" {
|
|
||||||
if !c.User.Permissions["allowPublish"] {
|
|
||||||
return http.StatusForbidden, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
|
||||||
|
|
||||||
// Before save command handler.
|
|
||||||
if err := c.Runner("before_publish", filename); err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We only run undraft command if it is a file.
|
|
||||||
if strings.HasSuffix(filename, ".md") && strings.HasSuffix(filename, ".markdown") {
|
|
||||||
if err := o.undraft(filename); err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regenerates the file
|
|
||||||
o.run(false)
|
|
||||||
|
|
||||||
// Executed the before publish command.
|
|
||||||
if err := c.Runner("before_publish", filename); err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return http.StatusOK, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Header.Get("Schedule") != "" {
|
|
||||||
if !c.User.Permissions["allowPublish"] {
|
|
||||||
return http.StatusForbidden, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return o.schedule(c, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
return http.StatusNotFound, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func previewHandler(c *filemanager.RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
||||||
h := c.Plugins["hugo"].(*Hugo)
|
|
||||||
|
|
||||||
// Get a new temporary path if there is none.
|
|
||||||
if h.previewPath == "" {
|
|
||||||
path, err := ioutil.TempDir("", "")
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
h.previewPath = path
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the arguments to execute Hugo: change the base URL,
|
|
||||||
// build the drafts and update the destination.
|
|
||||||
args := h.Args
|
|
||||||
args = append(args, "--baseURL", c.RootURL()+"/preview/")
|
|
||||||
args = append(args, "--buildDrafts")
|
|
||||||
args = append(args, "--destination", h.previewPath)
|
|
||||||
|
|
||||||
// Builds the preview.
|
|
||||||
if err := Run(h.Exe, args, h.Root); err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serves the temporary path with the preview.
|
|
||||||
http.FileServer(http.Dir(h.previewPath)).ServeHTTP(w, r)
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
|
@ -1,227 +0,0 @@
|
||||||
package plugins
|
|
||||||
|
|
||||||
const hugoJavaScript = `'use strict';
|
|
||||||
|
|
||||||
(function () {
|
|
||||||
if (window.plugins === undefined || window.plugins === null) {
|
|
||||||
window.plugins = []
|
|
||||||
}
|
|
||||||
|
|
||||||
let regenerate = function (data, url) {
|
|
||||||
url = data.api.removePrefix(url)
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let request = new window.XMLHttpRequest()
|
|
||||||
request.open('POST', data.store.state.baseURL + "/api/hugo" + url, true)
|
|
||||||
request.setRequestHeader('Authorization', "Bearer " + data.store.state.jwt)
|
|
||||||
request.setRequestHeader('Regenerate', 'true')
|
|
||||||
|
|
||||||
request.onload = () => {
|
|
||||||
if (request.status === 200) {
|
|
||||||
resolve()
|
|
||||||
} else {
|
|
||||||
reject(request.responseText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request.onerror = (error) => reject(error)
|
|
||||||
request.send()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let newArchetype = function (data, url, type) {
|
|
||||||
url = data.api.removePrefix(url)
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let request = new window.XMLHttpRequest()
|
|
||||||
request.open('POST', data.store.state.baseURL + "/api/hugo" + url, true)
|
|
||||||
request.setRequestHeader('Authorization',"Bearer " + data.store.state.jwt)
|
|
||||||
request.setRequestHeader('Archetype', encodeURIComponent(type))
|
|
||||||
|
|
||||||
request.onload = () => {
|
|
||||||
if (request.status === 200) {
|
|
||||||
resolve(request.getResponseHeader('Location'))
|
|
||||||
} else {
|
|
||||||
reject(request.responseText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request.onerror = (error) => reject(error)
|
|
||||||
request.send()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let schedule = function (data, file, date) {
|
|
||||||
file = data.api.removePrefix(file)
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let request = new window.XMLHttpRequest()
|
|
||||||
request.open('POST', data.store.state.baseURL + "/api/hugo" + file, true)
|
|
||||||
request.setRequestHeader('Authorization', "Bearer " + data.store.state.jwt)
|
|
||||||
request.setRequestHeader('Schedule', date)
|
|
||||||
|
|
||||||
request.onload = () => {
|
|
||||||
if (request.status === 200) {
|
|
||||||
resolve(request.getResponseHeader('Location'))
|
|
||||||
} else {
|
|
||||||
reject(request.responseText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request.onerror = (error) => reject(error)
|
|
||||||
request.send()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
window.plugins.push({
|
|
||||||
name: 'hugo',
|
|
||||||
credits: 'With a flavour of <a rel="noopener noreferrer" href="https://github.com/hacdias/caddy-hugo">Hugo</a>.',
|
|
||||||
header: {
|
|
||||||
visible: [
|
|
||||||
{
|
|
||||||
if: function (data, route) {
|
|
||||||
return (data.store.state.req.kind === 'editor' &&
|
|
||||||
!data.store.state.loading &&
|
|
||||||
data.store.state.user.allowEdit &
|
|
||||||
data.store.state.user.permissions.allowPublish)
|
|
||||||
},
|
|
||||||
click: function (event, data, route) {
|
|
||||||
event.preventDefault()
|
|
||||||
document.getElementById('save-button').click()
|
|
||||||
// TODO: wait for save to finish?
|
|
||||||
data.buttons.loading('publish')
|
|
||||||
|
|
||||||
regenerate(data, route.path)
|
|
||||||
.then(() => {
|
|
||||||
data.buttons.done('publish')
|
|
||||||
data.store.commit('showSuccess', 'Post published!')
|
|
||||||
data.store.commit('setReload', true)
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
data.buttons.done('publish')
|
|
||||||
data.store.commit('showError', error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
id: 'publish-button',
|
|
||||||
icon: 'send',
|
|
||||||
name: 'Publish'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
hidden: [
|
|
||||||
{
|
|
||||||
if: function (data, route) {
|
|
||||||
return (data.store.state.req.kind === 'editor' &&
|
|
||||||
!data.store.state.loading &&
|
|
||||||
data.store.state.req.metadata !== undefined &&
|
|
||||||
data.store.state.req.metadata !== null &&
|
|
||||||
data.store.state.user.permissions.allowPublish)
|
|
||||||
},
|
|
||||||
click: function (event, data, route) {
|
|
||||||
document.getElementById('save-button').click()
|
|
||||||
data.store.commit('showHover', 'schedule')
|
|
||||||
},
|
|
||||||
id: 'schedule-button',
|
|
||||||
icon: 'alarm',
|
|
||||||
name: 'Schedule'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
sidebar: [
|
|
||||||
{
|
|
||||||
click: function (event, data, route) {
|
|
||||||
data.router.push({ path: '/files/settings' })
|
|
||||||
},
|
|
||||||
icon: 'settings',
|
|
||||||
name: 'Hugo Settings'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
click: function (event, data, route) {
|
|
||||||
data.store.commit('showHover', 'new-archetype')
|
|
||||||
},
|
|
||||||
if: function (data, route) {
|
|
||||||
return data.store.state.user.allowNew
|
|
||||||
},
|
|
||||||
icon: 'merge_type',
|
|
||||||
name: 'Hugo New'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
click: function (event, data, route) {
|
|
||||||
window.open(data.store.state.baseURL + '/preview/')
|
|
||||||
},
|
|
||||||
icon: 'remove_red_eye',
|
|
||||||
name: 'Preview'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
prompts: [
|
|
||||||
{
|
|
||||||
name: 'new-archetype',
|
|
||||||
title: 'New file',
|
|
||||||
description: 'Create a new post based on an archetype. Your file will be created on content folder.',
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
name: 'file',
|
|
||||||
placeholder: 'File name'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
name: 'archetype',
|
|
||||||
placeholder: 'Archetype'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
ok: 'Create',
|
|
||||||
submit: function (event, data, route) {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
let file = event.currentTarget.querySelector('[name="file"]').value
|
|
||||||
let type = event.currentTarget.querySelector('[name="archetype"]').value
|
|
||||||
if (type === '') type = 'default'
|
|
||||||
|
|
||||||
data.store.commit('closeHovers')
|
|
||||||
|
|
||||||
newArchetype(data, '/' + file, type)
|
|
||||||
.then((url) => {
|
|
||||||
data.router.push({ path: url })
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
data.store.commit('showError', error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'schedule',
|
|
||||||
title: 'Schedule',
|
|
||||||
description: 'Pick a date and time to schedule the publication of this post.',
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
type: 'datetime-local',
|
|
||||||
name: 'date',
|
|
||||||
placeholder: 'Date'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
ok: 'Schedule',
|
|
||||||
submit: function (event, data, route) {
|
|
||||||
event.preventDefault()
|
|
||||||
data.buttons.loading('schedule')
|
|
||||||
|
|
||||||
let date = event.currentTarget.querySelector('[name="date"]').value
|
|
||||||
if (date === '') {
|
|
||||||
data.buttons.done('schedule')
|
|
||||||
data.store.commit('showError', 'The date must not be empty.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
schedule(data, route.path, date)
|
|
||||||
.then(() => {
|
|
||||||
data.buttons.done('schedule')
|
|
||||||
data.store.commit('showSuccess', 'Post scheduled!')
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
data.buttons.done('schedule')
|
|
||||||
data.store.commit('showError', error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
})()`
|
|
|
@ -1,19 +0,0 @@
|
||||||
package plugins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Run executes an external command
|
|
||||||
func Run(command string, args []string, path string) error {
|
|
||||||
cmd := exec.Command(command, args...)
|
|
||||||
cmd.Dir = path
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return errors.New(string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
28
resource.go
28
resource.go
|
@ -203,12 +203,40 @@ func resourcePostPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Re
|
||||||
return errorToHTTP(err, false), err
|
return errorToHTTP(err, false), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this instance has a Static Generator and handles publishing
|
||||||
|
// or scheduling if it's the case.
|
||||||
|
if c.StaticGen != nil {
|
||||||
|
code, err := resourcePublishSchedule(c, w, r)
|
||||||
|
if code != 0 {
|
||||||
|
return code, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Writes the ETag Header.
|
// Writes the ETag Header.
|
||||||
etag := fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.Size())
|
etag := fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.Size())
|
||||||
w.Header().Set("ETag", etag)
|
w.Header().Set("ETag", etag)
|
||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resourcePublishSchedule(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
publish := r.Header.Get("Publish")
|
||||||
|
schedule := r.Header.Get("Schedule")
|
||||||
|
|
||||||
|
if publish != "true" && schedule == "" {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.User.AllowPublish {
|
||||||
|
return http.StatusForbidden, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if publish == "true" {
|
||||||
|
return c.StaticGen.Publish(c, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.StaticGen.Schedule(c, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
// resourcePatchHandler is the entry point for resource handler.
|
// resourcePatchHandler is the entry point for resource handler.
|
||||||
func resourcePatchHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func resourcePatchHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
if !c.User.AllowEdit {
|
if !c.User.AllowEdit {
|
||||||
|
|
596
rice-box.go
596
rice-box.go
File diff suppressed because one or more lines are too long
48
settings.go
48
settings.go
|
@ -1,6 +1,7 @@
|
||||||
package filemanager
|
package filemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -11,12 +12,12 @@ import (
|
||||||
type modifySettingsRequest struct {
|
type modifySettingsRequest struct {
|
||||||
*modifyRequest
|
*modifyRequest
|
||||||
Data struct {
|
Data struct {
|
||||||
Commands map[string][]string `json:"commands"`
|
Commands map[string][]string `json:"commands"`
|
||||||
Plugins map[string]map[string]interface{} `json:"plugins"`
|
StaticGen map[string]interface{} `json:"staticGen"`
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type pluginOption struct {
|
type option struct {
|
||||||
Variable string `json:"variable"`
|
Variable string `json:"variable"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Value interface{} `json:"value"`
|
Value interface{} `json:"value"`
|
||||||
|
@ -59,8 +60,8 @@ func settingsHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
type settingsGetRequest struct {
|
type settingsGetRequest struct {
|
||||||
Commands map[string][]string `json:"commands"`
|
Commands map[string][]string `json:"commands"`
|
||||||
Plugins map[string][]pluginOption `json:"plugins"`
|
StaticGen []option `json:"staticGen"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func settingsGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func settingsGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
@ -69,19 +70,22 @@ func settingsGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Reques
|
||||||
}
|
}
|
||||||
|
|
||||||
result := &settingsGetRequest{
|
result := &settingsGetRequest{
|
||||||
Commands: c.Commands,
|
Commands: c.Commands,
|
||||||
Plugins: map[string][]pluginOption{},
|
StaticGen: []option{},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, p := range c.Plugins {
|
if c.StaticGen != nil {
|
||||||
result.Plugins[name] = []pluginOption{}
|
t := reflect.TypeOf(c.StaticGen).Elem()
|
||||||
|
|
||||||
t := reflect.TypeOf(p).Elem()
|
|
||||||
for i := 0; i < t.NumField(); i++ {
|
for i := 0; i < t.NumField(); i++ {
|
||||||
result.Plugins[name] = append(result.Plugins[name], pluginOption{
|
if t.Field(i).Name[0] == bytes.ToLower([]byte{t.Field(i).Name[0]})[0] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result.StaticGen = append(result.StaticGen, option{
|
||||||
Variable: t.Field(i).Name,
|
Variable: t.Field(i).Name,
|
||||||
Name: t.Field(i).Tag.Get("name"),
|
Name: t.Field(i).Tag.Get("name"),
|
||||||
Value: reflect.ValueOf(p).Elem().FieldByName(t.Field(i).Name).Interface(),
|
Value: reflect.ValueOf(c.StaticGen).Elem().FieldByName(t.Field(i).Name).Interface(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,18 +112,16 @@ func settingsPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Reques
|
||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the plugins.
|
// Update the static generator options.
|
||||||
if mod.Which == "plugins" {
|
if mod.Which == "staticGen" {
|
||||||
for name, plugin := range mod.Data.Plugins {
|
err = mapstructure.Decode(mod.Data.StaticGen, c.StaticGen)
|
||||||
err = mapstructure.Decode(plugin, c.Plugins[name])
|
if err != nil {
|
||||||
if err != nil {
|
return http.StatusInternalServerError, err
|
||||||
return http.StatusInternalServerError, err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
err = c.db.Set("plugins", name, c.Plugins[name])
|
err = c.db.Set("staticgen", c.staticgen, c.StaticGen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
|
|
|
@ -0,0 +1,239 @@
|
||||||
|
package filemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hacdias/varutils"
|
||||||
|
"github.com/robfig/cron"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrHugoNotFound ...
|
||||||
|
ErrHugoNotFound = errors.New("It seems that tou don't have 'hugo' on your PATH")
|
||||||
|
// ErrUnsupportedFileType ...
|
||||||
|
ErrUnsupportedFileType = errors.New("The type of the provided file isn't supported for this action")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hugo is the Hugo static website generator.
|
||||||
|
type Hugo struct {
|
||||||
|
// Website root
|
||||||
|
Root string `name:"Website Root"`
|
||||||
|
// Public folder
|
||||||
|
Public string `name:"Public Directory"`
|
||||||
|
// Hugo executable path
|
||||||
|
Exe string `name:"Hugo Executable"`
|
||||||
|
// Hugo arguments
|
||||||
|
Args []string `name:"Hugo Arguments"`
|
||||||
|
// Indicates if we should clean public before a new publish.
|
||||||
|
CleanPublic bool `name:"Clean Public"`
|
||||||
|
// previewPath is the temporary path for a preview
|
||||||
|
previewPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SettingsPath retrieves the correct settings path.
|
||||||
|
func (h Hugo) SettingsPath() string {
|
||||||
|
var frontmatter string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if _, err = os.Stat(filepath.Join(h.Root, "config.yaml")); err == nil {
|
||||||
|
frontmatter = "yaml"
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = os.Stat(filepath.Join(h.Root, "config.json")); err == nil {
|
||||||
|
frontmatter = "json"
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = os.Stat(filepath.Join(h.Root, "config.toml")); err == nil {
|
||||||
|
frontmatter = "toml"
|
||||||
|
}
|
||||||
|
|
||||||
|
if frontmatter == "" {
|
||||||
|
return "/settings"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "/config." + frontmatter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook is the pre-api handler.
|
||||||
|
func (h Hugo) Hook(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
// If we are not using HTTP Post, we shall return Method Not Allowed
|
||||||
|
// since we are only working with this method.
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Router != "resource" {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only care about creating new files from archetypes here. So...
|
||||||
|
if r.Header.Get("Archetype") == "" {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.User.AllowNew {
|
||||||
|
return http.StatusForbidden, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
||||||
|
archetype := r.Header.Get("archetype")
|
||||||
|
|
||||||
|
ext := filepath.Ext(filename)
|
||||||
|
|
||||||
|
// If the request isn't for a markdown file, we can't
|
||||||
|
// handle it.
|
||||||
|
if ext != ".markdown" && ext != ".md" {
|
||||||
|
return http.StatusBadRequest, ErrUnsupportedFileType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tries to create a new file based on this archetype.
|
||||||
|
args := []string{"new", filename, "--kind", archetype}
|
||||||
|
if err := runCommand(h.Exe, args, h.Root); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes the location of the new file to the Header.
|
||||||
|
w.Header().Set("Location", "/files/content/"+filename)
|
||||||
|
return http.StatusCreated, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish publishes a post.
|
||||||
|
func (h Hugo) Publish(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
filename := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
||||||
|
|
||||||
|
// Before save command handler.
|
||||||
|
if err := c.Runner("before_publish", filename); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only run undraft command if it is a file.
|
||||||
|
if strings.HasSuffix(filename, ".md") && strings.HasSuffix(filename, ".markdown") {
|
||||||
|
if err := h.undraft(filename); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regenerates the file
|
||||||
|
h.run(false)
|
||||||
|
|
||||||
|
// Executed the before publish command.
|
||||||
|
if err := c.Runner("before_publish", filename); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule schedules a post.
|
||||||
|
func (h Hugo) Schedule(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
t, err := time.Parse("2006-01-02T15:04", r.Header.Get("Schedule"))
|
||||||
|
path := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
||||||
|
path = filepath.Clean(path)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduler := cron.New()
|
||||||
|
scheduler.AddFunc(t.Format("05 04 15 02 01 *"), func() {
|
||||||
|
if err := h.undraft(path); err != nil {
|
||||||
|
log.Printf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
h.run(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
scheduler.Start()
|
||||||
|
return http.StatusOK, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preview handles the preview path.
|
||||||
|
func (h *Hugo) Preview(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
// Get a new temporary path if there is none.
|
||||||
|
if h.previewPath == "" {
|
||||||
|
path, err := ioutil.TempDir("", "")
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
h.previewPath = path
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the arguments to execute Hugo: change the base URL,
|
||||||
|
// build the drafts and update the destination.
|
||||||
|
args := h.Args
|
||||||
|
args = append(args, "--baseURL", c.RootURL()+"/preview/")
|
||||||
|
args = append(args, "--buildDrafts")
|
||||||
|
args = append(args, "--destination", h.previewPath)
|
||||||
|
|
||||||
|
// Builds the preview.
|
||||||
|
if err := runCommand(h.Exe, args, h.Root); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serves the temporary path with the preview.
|
||||||
|
http.FileServer(http.Dir(h.previewPath)).ServeHTTP(w, r)
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Hugo) run(force bool) {
|
||||||
|
// If the CleanPublic option is enabled, clean it.
|
||||||
|
if h.CleanPublic {
|
||||||
|
os.RemoveAll(h.Public)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent running if watching is enabled
|
||||||
|
if b, pos := varutils.StringInSlice("--watch", h.Args); b && !force {
|
||||||
|
if len(h.Args) > pos && h.Args[pos+1] != "false" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(h.Args) == pos+1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := runCommand(h.Exe, h.Args, h.Root); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Hugo) undraft(file string) error {
|
||||||
|
args := []string{"undraft", file}
|
||||||
|
if err := runCommand(h.Exe, args, h.Root); err != nil && !strings.Contains(err.Error(), "not a Draft") {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hugo) find() error {
|
||||||
|
var err error
|
||||||
|
if h.Exe, err = exec.LookPath("hugo"); err != nil {
|
||||||
|
return ErrHugoNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runCommand executes an external command
|
||||||
|
func runCommand(command string, args []string, path string) error {
|
||||||
|
cmd := exec.Command(command, args...)
|
||||||
|
cmd.Dir = path
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
5
users.go
5
users.go
|
@ -344,10 +344,7 @@ func usersPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
||||||
u.Password = suser.Password
|
u.Password = suser.Password
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default permissions if current are nil.
|
|
||||||
if u.Permissions == nil {
|
|
||||||
u.Permissions = c.DefaultUser.Permissions
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updates the whole User struct because we always are supposed
|
// Updates the whole User struct because we always are supposed
|
||||||
// to send a new entire object.
|
// to send a new entire object.
|
||||||
|
|
Loading…
Reference in New Issue