Jonatan Nilsson
71b1725655
Some checks failed
continuous-integration/appveyor/branch AppVeyor build failed
427 lines
13 KiB
JavaScript
427 lines
13 KiB
JavaScript
require('./dtsel')
|
|
const FileUpload = require('./fileupload')
|
|
const PageTree = require('../page_tree')
|
|
const Fileinfo = require('../fileinfo')
|
|
const api = require('../api')
|
|
const Editor = require('./editor')
|
|
const Dialogue = require('./dialogue')
|
|
|
|
const EditArticle = {
|
|
|
|
oninit: function(vnode) {
|
|
this.loading = false
|
|
this.showLoading = null
|
|
this.data = {
|
|
article: null,
|
|
files: [],
|
|
staff: [],
|
|
}
|
|
this.pages = [{id: null, name: 'Frontpage'}]
|
|
this.pages = this.pages.concat(PageTree.getFlatTree())
|
|
|
|
this.removeBanner = false
|
|
this.removeMedia = false
|
|
this.newBanner = null
|
|
this.newMedia = null
|
|
this.dateInstance = null
|
|
this.editor = null
|
|
|
|
this.fetchArticle(vnode)
|
|
},
|
|
|
|
onbeforeupdate: function(vnode) {
|
|
if (this.lastid !== m.route.param('id')) {
|
|
this.fetchArticle(vnode)
|
|
}
|
|
},
|
|
|
|
fetchArticle: function(vnode) {
|
|
this.lastid = m.route.param('id')
|
|
|
|
return this.requestArticle(
|
|
api.sendRequest({
|
|
method: 'GET',
|
|
url: '/api/auth/articles/' + (this.lastid === 'add' ? '0' : this.lastid),
|
|
})
|
|
)
|
|
},
|
|
|
|
requestArticle: function(data) {
|
|
this.error = ''
|
|
|
|
if (this.showLoading) {
|
|
clearTimeout(this.showLoading)
|
|
}
|
|
|
|
if (this.data.article) {
|
|
this.showLoading = setTimeout(() => {
|
|
this.showLoading = null
|
|
this.loading = true
|
|
m.redraw()
|
|
}, 150)
|
|
} else {
|
|
this.loading = true
|
|
}
|
|
|
|
data
|
|
.then((result) => {
|
|
this.data = result
|
|
|
|
if (this.data.article.id) {
|
|
this.data.article.publish_at = new Date(this.data.article.publish_at)
|
|
document.title = 'Editing: ' + this.data.article.name + ' - Admin NFP Moe'
|
|
this.editedPath = true
|
|
} else {
|
|
this.data.article.publish_at = new Date('3000-01-01')
|
|
document.title = 'Create Article - Admin NFP Moe'
|
|
}
|
|
}, (err) => {
|
|
this.error = err.message
|
|
})
|
|
.then(() => {
|
|
clearTimeout(this.showLoading)
|
|
this.showLoading = null
|
|
this.loading = false
|
|
m.redraw()
|
|
})
|
|
},
|
|
|
|
updateValue: function(name, e) {
|
|
if (name === 'is_featured') {
|
|
this.data.article[name] = e.currentTarget.checked
|
|
} else {
|
|
this.data.article[name] = e.currentTarget.value
|
|
}
|
|
if (name === 'path') {
|
|
this.editedPath = true
|
|
} else if (name === 'name' && !this.editedPath) {
|
|
this.data.article.path = this.data.article.name
|
|
.normalize("NFD").replace(/[\u0300-\u036f]/g, '')
|
|
.toLocaleLowerCase()
|
|
.replace(/[^a-z0-9]/g, '-')
|
|
.replace(/\-{2,}/g, '-')
|
|
.replace(/^-+/, '')
|
|
.replace(/-+$/, '')
|
|
}
|
|
},
|
|
|
|
updateParent: function(e) {
|
|
this.data.article.page_id = Number(e.currentTarget.value) || null
|
|
},
|
|
|
|
updateStaffer: function(e) {
|
|
this.data.article.admin_id = Number(e.currentTarget.value)
|
|
},
|
|
|
|
mediaUploaded: function(type, file) {
|
|
if (type === 'banner') {
|
|
this.newBanner = file
|
|
this.removeBanner = false
|
|
} else {
|
|
this.newMedia = file
|
|
this.removeMedia = false
|
|
}
|
|
},
|
|
|
|
mediaRemoved: function(type) {
|
|
this.data.article[type + '_alt_prefix'] = null
|
|
if (type === 'banner') {
|
|
this.removeBanner = true
|
|
} else {
|
|
this.removeMedia = true
|
|
}
|
|
},
|
|
|
|
save: function(vnode, e) {
|
|
e.preventDefault()
|
|
if (!this.data.article.name) {
|
|
this.error = 'Name is missing'
|
|
} else if (!this.data.article.path) {
|
|
this.error = 'Path is missing'
|
|
} else {
|
|
this.error = ''
|
|
}
|
|
if (this.error) return
|
|
|
|
let formData = new FormData()
|
|
if (this.newBanner) {
|
|
formData.append('banner', this.newBanner.file)
|
|
}
|
|
if (this.newMedia) {
|
|
formData.append('media', this.newMedia.file)
|
|
}
|
|
if (this.data.article.id) {
|
|
formData.append('id', this.data.article.id)
|
|
}
|
|
|
|
formData.append('admin_id', this.data.article.admin_id || this.data.staff[0].id)
|
|
formData.append('name', this.data.article.name)
|
|
formData.append('is_featured', this.data.article.is_featured || false)
|
|
formData.append('path', this.data.article.path)
|
|
formData.append('page_id', this.data.article.page_id || null)
|
|
formData.append('publish_at', this.dateInstance.inputElem.value.replace(', ', 'T') + 'Z')
|
|
formData.append('remove_banner', this.removeBanner ? true : false)
|
|
formData.append('remove_media', this.removeMedia ? true : false)
|
|
|
|
this.loading = true
|
|
|
|
this.requestArticle(
|
|
this.editor.save()
|
|
.then(body => {
|
|
formData.append('content', JSON.stringify(body))
|
|
|
|
return api.sendRequest({
|
|
method: 'PUT',
|
|
url: '/api/auth/articles/' + (this.lastid === 'add' ? '0' : this.lastid),
|
|
body: formData,
|
|
})
|
|
})
|
|
.then(data => {
|
|
if (!data.article.id) {
|
|
throw new Error('Something went wrong with saving, try again later')
|
|
} else if (this.lastid === 'add') {
|
|
this.lastid = data.article.id.toString()
|
|
m.route.set('/admin/articles/' + data.article.id)
|
|
}
|
|
return data
|
|
})
|
|
)
|
|
},
|
|
|
|
uploadFile: function(vnode, e) {
|
|
if (!e.target.files[0]) return
|
|
if (this.lastid === 'add') return
|
|
|
|
let file = e.target.files[0]
|
|
e.target.value = null
|
|
|
|
let formData = new FormData()
|
|
formData.append('file', file)
|
|
|
|
return this.refreshFiles(vnode, api.sendRequest({
|
|
method: 'POST',
|
|
url: '/api/auth/articles/' + this.lastid + '/files',
|
|
body: formData,
|
|
}))
|
|
},
|
|
|
|
refreshFiles: function(vnode, prom) {
|
|
this.loading = true
|
|
m.redraw()
|
|
|
|
prom.then(() => {
|
|
return api.sendRequest({
|
|
method: 'GET',
|
|
url: '/api/auth/articles/' + this.lastid,
|
|
})
|
|
})
|
|
.then((result) => {
|
|
this.data.files = result.files
|
|
}, (err) => {
|
|
this.error = err.message
|
|
})
|
|
.then(() => {
|
|
this.loading = false
|
|
m.redraw()
|
|
})
|
|
},
|
|
|
|
confirmRemoveFile: function(vnode, file) {
|
|
return this.refreshFiles(vnode, api.sendRequest({
|
|
method: 'DELETE',
|
|
url: '/api/auth/articles/' + this.lastid + '/files/' + file.id,
|
|
}))
|
|
},
|
|
|
|
askConfirmRemoveFile: function(vnode, file) {
|
|
Dialogue.showDialogue(
|
|
'Delete file',
|
|
'Are you sure you want to remove "' + file.filename + '"',
|
|
'Delete',
|
|
'alert',
|
|
'Don\'t delete',
|
|
'',
|
|
file,
|
|
this.confirmRemoveFile.bind(this, vnode))
|
|
},
|
|
|
|
view: function(vnode) {
|
|
let article = this.data.article
|
|
const showPublish = article
|
|
? article.publish_at > new Date()
|
|
: false
|
|
const bannerImage = article && article.banner_alt_prefix
|
|
? article.banner_alt_prefix + '_large.avif'
|
|
: null
|
|
const mediaImage = article && article.media_alt_prefix
|
|
? article.media_alt_prefix + '_large.avif'
|
|
: null
|
|
|
|
return [
|
|
m('div.admin', [
|
|
!this.loading
|
|
? m(FileUpload, {
|
|
class: 'banner',
|
|
height: 150,
|
|
onfile: this.mediaUploaded.bind(this, 'banner'),
|
|
ondelete: this.mediaRemoved.bind(this, 'banner'),
|
|
media: bannerImage,
|
|
}, 'Click to upload banner image (only visible when featured)')
|
|
: null,
|
|
m('div.inside.vertical', [
|
|
m('div.actions', [
|
|
'« ',
|
|
m(m.route.Link, { href: '/admin/articles' }, 'Articles'),
|
|
article && article.id
|
|
? [
|
|
m('div.filler'),
|
|
m('span', 'Actions:'),
|
|
m(m.route.Link, { href: '/article/' + article.path }, 'View article'),
|
|
]
|
|
: null,
|
|
]),
|
|
m('h2.title', this.lastid === 'add' ? 'Create article' : 'Edit ' + (article && article.name || '(untitled)')),
|
|
m('div.container', [
|
|
m('div.error', {
|
|
hidden: !this.error,
|
|
onclick: function() { vnode.state.error = '' },
|
|
}, this.error),
|
|
this.loading
|
|
? m('div.loading-spinner')
|
|
: null,
|
|
article
|
|
? [
|
|
m(FileUpload, {
|
|
class: 'cover',
|
|
useimg: true,
|
|
onfile: this.mediaUploaded.bind(this, 'media'),
|
|
ondelete: this.mediaRemoved.bind(this, 'media'),
|
|
media: mediaImage,
|
|
}, 'Click to upload article image'),
|
|
m('form', {
|
|
onsubmit: this.save.bind(this, vnode),
|
|
}, [
|
|
m('label', 'Parent'),
|
|
m('select', {
|
|
onchange: this.updateParent.bind(this),
|
|
}, this.pages.map((item) => {
|
|
return m('option', {
|
|
value: item.id || 0,
|
|
selected: item.id === article.page_id
|
|
}, item.name)
|
|
})),
|
|
m('div.input-row', [
|
|
m('div.input-group', [
|
|
m('label', 'Name'),
|
|
m('input', {
|
|
type: 'text',
|
|
value: article.name,
|
|
oninput: this.updateValue.bind(this, 'name'),
|
|
}),
|
|
]),
|
|
m('div.input-group', [
|
|
m('label', 'Path'),
|
|
m('input', {
|
|
type: 'text',
|
|
value: article.path,
|
|
oninput: this.updateValue.bind(this, 'path'),
|
|
}),
|
|
]),
|
|
]),
|
|
m('label', 'Description'),
|
|
m(Editor, {
|
|
oncreate: (subnode) => {
|
|
this.editor = subnode.state.editor
|
|
},
|
|
contentdata: article.content,
|
|
}),
|
|
m('div.input-row', [
|
|
m('div', [
|
|
m('label', 'Published at'),
|
|
m('input', {
|
|
type: 'text',
|
|
oncreate: (div) => {
|
|
if (!this.dateInstance) {
|
|
this.dateInstance = new dtsel.DTS(div.dom, {
|
|
dateFormat: 'yyyy-mm-dd',
|
|
timeFormat: 'HH:MM:SS',
|
|
showTime: true,
|
|
})
|
|
window.temp = this.dateInstance
|
|
}
|
|
},
|
|
value: article.publish_at.toISOString().replace('T', ', ').split('.')[0],
|
|
}),
|
|
]),
|
|
m('div', [
|
|
m('label', 'Published by'),
|
|
m('select', {
|
|
onchange: this.updateStaffer.bind(this),
|
|
},
|
|
this.data.staff.map((item) => {
|
|
return m('option', {
|
|
value: item.id,
|
|
selected: item.id === article.admin_id
|
|
}, item.name)
|
|
})
|
|
),
|
|
]),
|
|
m('div.slim', [
|
|
m('label', 'Make featured'),
|
|
m('input', {
|
|
type: 'checkbox',
|
|
checked: article.is_featured,
|
|
oninput: this.updateValue.bind(this, 'is_featured'),
|
|
}),
|
|
]),
|
|
]),
|
|
m('div.actions', {
|
|
hidden: !article.name || !article.path
|
|
}, [
|
|
m('input', {
|
|
type: 'submit',
|
|
value: article.id ? 'Save' : 'Create',
|
|
}),
|
|
showPublish
|
|
? m('button', {
|
|
onclick: () => {
|
|
this.dateInstance.inputElem.value = (new Date().toISOString()).replace('T', ', ').split('.')[0]
|
|
}
|
|
}, 'Publish now')
|
|
: null,
|
|
]),
|
|
]),
|
|
m('files', [
|
|
m('h5', 'Files'),
|
|
this.data.files.map((file) => {
|
|
return m(
|
|
Fileinfo,
|
|
{ file: file },
|
|
m('div.remove',
|
|
m('button', { onclick: () => this.askConfirmRemoveFile(vnode, file) }, 'remove')
|
|
)
|
|
)
|
|
}),
|
|
]),
|
|
article.id
|
|
? m('div.actions', [
|
|
m('button.fileupload', [
|
|
'Add file',
|
|
m('input', {
|
|
accept: '*',
|
|
type: 'file',
|
|
onchange: this.uploadFile.bind(this, vnode),
|
|
}),
|
|
])
|
|
])
|
|
: null,
|
|
]
|
|
: null,
|
|
]),
|
|
]),
|
|
]),
|
|
]
|
|
},
|
|
}
|
|
|
|
module.exports = EditArticle
|