mirror of
				https://github.com/filebrowser/filebrowser.git
				synced 2025-10-31 17:23:09 +00:00 
			
		
		
		
	StaticGen update Bash
Update zh-cn.yaml (#194) Address #184 build assets Update zh-cn.yaml (#194) Former-commit-id: 4572f93371647c0a6b53cc375ec5bb00356a37c9 [formerly e58e4793ac0ca6915b605d7b2a8a77f0aec31172] [formerly 43635f6b98f546ec0e2656d26031388aef63a902 [formerly da4fd84002f4cac1d4e04f65c96237a553203d83]] Former-commit-id: 15422887ad29dea63bdf861d9da8f8d28b4fbc8f [formerly 3949ffa499cf999c3f4b50ee18802c0f87a23807] Former-commit-id: fac5ceeee3fa969239d9ef5e04ef5542a61d2761
This commit is contained in:
		
							parent
							
								
									bfdb924cb7
								
							
						
					
					
						commit
						d5e943069e
					
				assets
caddy/hugo
cmd/filemanager
filemanager.gohttp.goplugins
resource.gorice-box.go.REMOVED.git-idsettings.gostaticgen.gousers.go| @ -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 | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										21
									
								
								assets/src/components/buttons/Schedule.vue
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
								
							
						
						
									
										21
									
								
								assets/src/components/buttons/Schedule.vue
									
									
									
									
									
										Normal file
									
								
							| @ -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', '') | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | |||||||
							
								
								
									
										68
									
								
								assets/src/components/prompts/NewArchetype.vue
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
								
							
						
						
									
										68
									
								
								assets/src/components/prompts/NewArchetype.vue
									
									
									
									
									
										Normal file
									
								
							| @ -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, | ||||||
|  | |||||||
							
								
								
									
										41
									
								
								assets/src/components/prompts/Schedule.vue
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
								
							
						
						
									
										41
									
								
								assets/src/components/prompts/Schedule.vue
									
									
									
									
									
										Normal file
									
								
							| @ -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 { | ||||||
|  | |||||||
| @ -1 +1 @@ | |||||||
| 824bbb2a05401392205f299b61d082c54ed3f3d2 | fb7f000455844aaf5021aa2a43f0fcd05f67fa35 | ||||||
							
								
								
									
										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 | ||||||
|  | |||||||
							
								
								
									
										239
									
								
								staticgen.go
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
								
							
						
						
									
										239
									
								
								staticgen.go
									
									
									
									
									
										Normal file
									
								
							| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Henrique Dias
						Henrique Dias