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