diff --git a/nfp_moe/app/admin/admin.js b/nfp_moe/app/admin/admin.js index 9a58f03..2d5ae8d 100644 --- a/nfp_moe/app/admin/admin.js +++ b/nfp_moe/app/admin/admin.js @@ -4,9 +4,16 @@ const AllArticles = require('./site_articles') const EditArticle = require('./editarticle') const AllStaff = require('./stafflist') const EditStaff = require('./editstaff') +const Dialogue = require('./dialogue') window.adminRoutes = { pages: [AllPages, EditPage], articles: [AllArticles, EditArticle], staff: [AllStaff, EditStaff], } + +let dialogueMount = document.createElement('div') +dialogueMount.id = 'dialogue_mount' +document.body.appendChild(dialogueMount) + +m.mount(dialogueMount, Dialogue) diff --git a/nfp_moe/app/admin/dialogue.js b/nfp_moe/app/admin/dialogue.js index e2ac9a1..e74cc0d 100644 --- a/nfp_moe/app/admin/dialogue.js +++ b/nfp_moe/app/admin/dialogue.js @@ -1,16 +1,44 @@ const Dialogue = { + showDialogueData: null, + + showDialogue: function(title, message, yes, yesclass, no, noclass, data, cb) { + Dialogue.showDialogueData = { + title: title, + message: message, + yes: yes, + yesclass: yesclass || '', + no: no, + noclass: noclass || '', + data: data, + cb: cb, + } + }, + + onclick: function(clickedYes) { + if (clickedYes) { + Dialogue.showDialogueData.cb(Dialogue.showDialogueData.data) + } + this.onclose() + m.redraw() + }, + + onclose: function() { + Dialogue.showDialogueData = null + }, + view: function(vnode) { - return m('div.floating-container', { - hidden: vnode.attrs.hidden, - }, m('dialogue', [ - m('h2', vnode.attrs.title), - m('p', vnode.attrs.message), - m('div.buttons', [ - m('button', { class: vnode.attrs.yesclass || '', onclick: vnode.attrs.onyes }, vnode.attrs.yes), - m('button', { class: vnode.attrs.noclass || '', onclick: vnode.attrs.onno }, vnode.attrs.no), - ]), - ]) - ) + let data = Dialogue.showDialogueData + return data + ? m('div.floating-container.main', m('dialogue', [ + m('h2.title', data.title), + m('p', data.message), + m('div.buttons', [ + m('button', { class: data.yesclass , onclick: this.onclick.bind(this) }, data.yes), + m('button', { class: data.noclass, onclick: this.onclose.bind(this) }, data.no), + ]), + ]) + ) + : null }, } diff --git a/nfp_moe/app/admin/fileupload.js b/nfp_moe/app/admin/fileupload.js index 01beed4..8610e11 100644 --- a/nfp_moe/app/admin/fileupload.js +++ b/nfp_moe/app/admin/fileupload.js @@ -51,12 +51,15 @@ const FileUpload = { : null , !imageLink - ? m('div.noimage', { - style: { - 'padding-top': vnode.attrs.useimg ? '56.25%' : null, - 'height': !vnode.attrs.useimg ? vnode.attrs.height + 'px' : null, - }, - }, vnode.children) + ? [ + m('div.noimage', { + style: { + 'padding-top': vnode.attrs.useimg ? '56.25%' : null, + 'height': !vnode.attrs.useimg ? vnode.attrs.height + 'px' : null, + }, + }), + m('div.text', vnode.children) + ] : null, m('input', { accept: 'image/*', diff --git a/nfp_moe/app/admin/site_articles.js b/nfp_moe/app/admin/site_articles.js index 7d62457..df71cfc 100644 --- a/nfp_moe/app/admin/site_articles.js +++ b/nfp_moe/app/admin/site_articles.js @@ -114,7 +114,7 @@ const AdminArticles = { m('div.admin', [ m('div.inside.vertical', [ m('div.spacer'), - m('h2', 'All articles'), + m('h2.title', 'All articles'), m('div.actions', [ m('span', 'Actions:'), m(m.route.Link, { href: '/admin/articles/add' }, 'Create new article'), diff --git a/nfp_moe/app/admin/site_editpage.js b/nfp_moe/app/admin/site_editpage.js index 5128539..56f567c 100644 --- a/nfp_moe/app/admin/site_editpage.js +++ b/nfp_moe/app/admin/site_editpage.js @@ -189,7 +189,7 @@ const AdminEditPage = { '« ', m(m.route.Link, { href: '/admin/pages' }, 'Pages') ]), - m('h2', this.lastid === 'add' ? 'Create page' : 'Edit ' + (page && page.name || '(untitled)')), + m('h2.title', this.lastid === 'add' ? 'Create page' : 'Edit ' + (page && page.name || '(untitled)')), page ? m('div.actions', [ m('span', 'Actions:'), @@ -239,12 +239,14 @@ const AdminEditPage = { ]), ]), m('label', 'Description'), - m(Editor, { - oncreate: (subnode) => { - this.editor = subnode.state.editor - }, - contentdata: page.content, - }), + m('div.content', + m(Editor, { + oncreate: (subnode) => { + this.editor = subnode.state.editor + }, + contentdata: page.content, + }) + ), m('input', { hidden: !page.name || !page.path, type: 'submit', diff --git a/nfp_moe/app/admin/site_pages.js b/nfp_moe/app/admin/site_pages.js index 9deb815..17eea78 100644 --- a/nfp_moe/app/admin/site_pages.js +++ b/nfp_moe/app/admin/site_pages.js @@ -31,7 +31,7 @@ const AdminPages = { }) }, - confirmRemovePage: function(vnode) { + confirmRemovePage: function(vnode, page) { let removingPage = this.removePage this.removePage = null this.loading = true @@ -52,6 +52,18 @@ const AdminPages = { ) }, + askConfirmRemovePage: function(vnode, page) { + Dialogue.showDialogue( + 'Delete ' + page.name, + 'Are you sure you want to remove "' + page.name + '" (' + page.path + ')', + 'Remove', + 'alert', + 'Don\'t remove', + '', + page, + this.confirmRemovePage.bind(this)) + }, + drawPage: function(vnode, page) { return [ m('tr', [ @@ -61,7 +73,7 @@ const AdminPages = { ]), m('td', m(m.route.Link, { href: '/page/' + page.path }, '/page/' + page.path)), m('td.right', page.updated_at.replace('T', ' ').split('.')[0]), - m('td.right', m('button', { onclick: function() { vnode.state.removePage = page } }, 'Remove')), + m('td.right', m('button', { onclick: this.askConfirmRemovePage.bind(this, vnode, page) }, 'Remove')), ]), ].concat(page.children ? page.children.map(AdminPages.drawPage.bind(this, vnode)) : []) }, @@ -71,7 +83,7 @@ const AdminPages = { m('div.admin', [ m('div.inside.vertical', [ m('div.spacer'), - m('h2', 'All pages'), + m('h2.title', 'All pages'), m('div.actions', [ m('span', 'Actions:'), m(m.route.Link, { href: '/admin/pages/add' }, 'Create new page'), @@ -100,17 +112,6 @@ const AdminPages = { }),*/ ]), ]), - m(Dialogue, { - hidden: vnode.state.removePage === null, - title: 'Delete ' + (vnode.state.removePage ? vnode.state.removePage.name : ''), - message: 'Are you sure you want to remove "' + (vnode.state.removePage ? vnode.state.removePage.name : '') + '" (' + (vnode.state.removePage ? vnode.state.removePage.path : '') + ')', - yes: 'Remove', - yesclass: 'alert', - no: 'Cancel', - noclass: 'cancel', - onyes: this.confirmRemovePage.bind(this, vnode), - onno: function() { vnode.state.removePage = null }, - }), ] }, } diff --git a/nfp_moe/app/article.js b/nfp_moe/app/article.js index 9cfe385..d2294c0 100644 --- a/nfp_moe/app/article.js +++ b/nfp_moe/app/article.js @@ -37,7 +37,7 @@ const Article = { }, [ m(m.route.Link, { href: '/article/' + article.path, class: 'title' }, - m('h2', article.name) + m('h2.title', article.name) ), m('div.row', [ media.getArticlePicture( @@ -47,7 +47,7 @@ const Article = { 'Image for news item ' + article.name, ), m('div', [ - m('div.description', + m('div.description.content', article.content.blocks.map(block => { return m(EditorBlock, { block: block }) }), diff --git a/nfp_moe/app/editorblock.js b/nfp_moe/app/editorblock.js index 08e74a4..ad78f93 100644 --- a/nfp_moe/app/editorblock.js +++ b/nfp_moe/app/editorblock.js @@ -41,12 +41,30 @@ const EditorBlock = { let block = vnode.attrs.block this.id = block.id switch (block.type) { + case 'header': + this.output = m('h' + block.data.level, block.data.text) + break; case 'paragraph': this.output = m('p', m.trust(block.data.text)) break case 'htmlraw': this.output = m.trust(block.data.html) break + case 'quote': + this.output = m('blockquote', [ + m('p', block.data.text), + m('span.author', block.data.caption), + ]) + break + case 'list': + this.output = m( + block.data.style === 'unordered' ? 'ul' : 'ol', + block.data.items.map(item => m('li', item)) + ) + break + case 'code': + this.output = m('pre', block.data.code) + break default: this.output = m('p', m.trust(block)) break diff --git a/nfp_moe/app/site_page.js b/nfp_moe/app/site_page.js index 9cbf4ff..f2e0d50 100644 --- a/nfp_moe/app/site_page.js +++ b/nfp_moe/app/site_page.js @@ -6,6 +6,7 @@ const Paginator = require('./paginator') const Article = require('./article') const Articleslim = require('./article_slim') const media = require('./media') +const EditorBlock = require('./editorblock') const ArticlesPerPage = 10 @@ -144,13 +145,22 @@ const SitePage = { : null), (page ? m('.inside.vertical', [ - m('div.page-goback', ['« ', m(m.route.Link, { - href: page.parent_path - ? '/page/' + page.parent_path - : '/' - }, page.parent_name || 'Home')] - ), - m('h2', page.name) + m('div.page-goback', [ + '« ', + m(m.route.Link, { + href: page.parent_path + ? '/page/' + page.parent_path + : '/' + }, page.parent_name || 'Home'), + Authentication.currentUser + ? [ + m('div.filler'), + 'Actions:', + m(m.route.Link, { href: '/admin/pages/' + page.id }, 'Edit page'), + ] + : null, + ]), + m('h2.title', page.name) ]) : null), m('.inside', [ @@ -174,9 +184,9 @@ const SitePage = { ? media.getArticlePicture(this.picture, false, page.media_path, 'Image for page ' + page.name) : null), (page && page.content - ? page.content.blocks.map(block => { + ? m('div.content', page.content.blocks.map(block => { return m(EditorBlock, { block: block }) - }) + })) : null), (page && this.data.articles.length ? [ diff --git a/nfp_moe/public/assets/admin.css b/nfp_moe/public/assets/admin.css index bbddd66..6e90297 100644 --- a/nfp_moe/public/assets/admin.css +++ b/nfp_moe/public/assets/admin.css @@ -26,7 +26,7 @@ } .admin .inside { - padding-bottom: 1rem; + padding: 0 1rem 1rem; } .admin .spacer { @@ -137,9 +137,18 @@ fileupload a { background-size: cover; } -fileupload .noimage { +fileupload .text { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + text-align: center; + justify-content: center; + flex-direction: column; color: var(--seperator); - } fileupload input { @@ -155,6 +164,71 @@ fileupload input { z-index: 2; } +/* ************** dialogue ************** */ + +.floating-container { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: #00000099; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +dialogue { + background: var(--bg); + display: flex; + flex-direction: column; + text-align: center; + width: calc(100% - 40px); + max-width: 500px; + color: var(--color); +} + +dialogue h2 { +} + +dialogue p { + padding: 1rem; +} + +dialogue .buttons { + display: flex; + justify-content: space-around; + padding: 1rem +} + +dialogue button { + border: 1px solid var(--link); + background: transparent; + color: var(--link); + padding: 0.5rem 1rem; + min-width: 150px; +} + +dialogue button.alert { + border: none; + background: var(--error-bg); + color: var(--error-fg); +} + +dialogue button.cancel { + border-color: var(--seperator); + color: var(--seperator); +} + +/* + ===================== Blocks ===================== +*/ + +.admin .cdx-quote__text { + min-height: 80px !important; +} + /* ===================== 3rd party ===================== */ diff --git a/nfp_moe/public/assets/app.css b/nfp_moe/public/assets/app.css index 28fe5d7..89bd92f 100644 --- a/nfp_moe/public/assets/app.css +++ b/nfp_moe/public/assets/app.css @@ -14,6 +14,7 @@ --primary-link: #f57c00; --bg: #fff; + --bg-content-alt: #eee; --color: #000; --light: #757575; --link: #bb4d00; @@ -189,6 +190,10 @@ button { padding: 1rem; } +.filler { + flex: 2 1 auto; +} + .wrapper .inside { flex-direction: column; color: var(--alt-color); @@ -332,6 +337,11 @@ main { .page-goback { padding: 0.5rem 1rem; + display: flex; +} + +.page-goback a { + margin-left: 0.375rem; } main a, @@ -352,7 +362,8 @@ main .loading-spinner { top: 50%; } -main h2 { +main h2.title, +.main h2.title { font-size: 1.4rem; background: var(--title-bg); color: var(--title-fg); @@ -567,6 +578,28 @@ fileinfo ul { margin-top: 1rem; } +/* + ===================== content ===================== +*/ + +.content :is(h1, h2, h3, h4, h5, ul, ol, blockquote, p) { + margin: 0 0 0.75rem; +} + +.content :is(h1, h2, h3, h4, h5) { + padding: 0 0.5rem 0.5rem; + border-bottom: 1px solid var(--seperator); +} + +.content :is(blockquote, pre) { + background: var(--bg-content-alt); + padding: 0.5rem; +} + +.content blockquote p { + margin: 0; +} + /* ===================== footer ===================== */