diff --git a/src/main/java/com/tale/controller/ArticleController.java b/src/main/java/com/tale/controller/ArticleController.java index 80d7aa26..5a701668 100644 --- a/src/main/java/com/tale/controller/ArticleController.java +++ b/src/main/java/com/tale/controller/ArticleController.java @@ -45,11 +45,10 @@ public class ArticleController extends BaseController { */ @GetRoute(value = {"/:cid", "/:cid.html"}) public String page(@PathParam String cid, Request request) { - Optional contentsOptional = contentsService.getContents(cid); - if (!contentsOptional.isPresent()) { + Contents contents = contentsService.getContents(cid); + if (null == contents) { return this.render_404(); } - Contents contents = contentsOptional.get(); if (contents.getAllowComment()) { int cp = request.queryInt("cp", 1); request.attribute("cp", cp); @@ -72,11 +71,10 @@ public String page(@PathParam String cid, Request request) { */ @GetRoute(value = {"article/:cid", "article/:cid.html"}) public String post(Request request, @PathParam String cid) { - Optional contentsOptional = contentsService.getContents(cid); - if (!contentsOptional.isPresent()) { + Contents contents = contentsService.getContents(cid); + if (null == contents) { return this.render_404(); } - Contents contents = contentsOptional.get(); if (Types.DRAFT.equals(contents.getStatus())) { return this.render_404(); } diff --git a/src/main/java/com/tale/controller/admin/AdminApiController.java b/src/main/java/com/tale/controller/admin/AdminApiController.java index 6abd7a63..a4257df9 100644 --- a/src/main/java/com/tale/controller/admin/AdminApiController.java +++ b/src/main/java/com/tale/controller/admin/AdminApiController.java @@ -14,19 +14,16 @@ import com.tale.service.MetasService; import com.tale.service.SiteService; import com.tale.validators.CommonValidator; -import io.github.biezhi.anima.enums.OrderBy; import io.github.biezhi.anima.page.Page; import java.util.List; import java.util.Optional; -import static io.github.biezhi.anima.Anima.select; - /** * @author biezhi * @date 2018/6/9 */ -@Path(value = "admin", suffix = ".json", restful = true) +@Path(value = "admin/api", restful = true) public class AdminApiController extends BaseController { @Inject @@ -40,8 +37,8 @@ public class AdminApiController extends BaseController { @GetRoute("articles/:cid") public RestResponse article(@PathParam String cid) { - Optional contents = contentsService.getContents(cid); - return RestResponse.ok(contents.orElse(null)); + Contents contents = contentsService.getContents(cid); + return RestResponse.ok(contents); } @PostRoute("article/new") @@ -83,15 +80,17 @@ public RestResponse updateArticle(@BodyParam Contents contents) { @GetRoute("articles") public RestResponse articleList(ArticleParam articleParam) { - Page articles = select().from(Contents.class).where(Contents::getType, Types.ARTICLE) - .order(Contents::getCreated, OrderBy.DESC).page(articleParam.getPage(), articleParam.getLimit()); + articleParam.setType(Types.ARTICLE); + articleParam.setOrderBy("created desc"); + Page articles = contentsService.findArticles(articleParam); return RestResponse.ok(articles); } @GetRoute("pages") public RestResponse pageList(ArticleParam articleParam) { - Page articles = select().from(Contents.class).where(Contents::getType, Types.PAGE) - .order(Contents::getCreated, OrderBy.DESC).page(articleParam.getPage(), articleParam.getLimit()); + articleParam.setType(Types.PAGE); + articleParam.setOrderBy("created desc"); + Page articles = contentsService.findArticles(articleParam); return RestResponse.ok(articles); } diff --git a/src/main/java/com/tale/controller/admin/PagesController.java b/src/main/java/com/tale/controller/admin/PagesController.java index eb802347..946551ab 100644 --- a/src/main/java/com/tale/controller/admin/PagesController.java +++ b/src/main/java/com/tale/controller/admin/PagesController.java @@ -63,6 +63,11 @@ public String commonPage(@PathParam String module, @PathParam String page) { return "admin/" + module + "/" + page + ".html"; } + @GetRoute("/article/edit/:cid") + public String editArticle() { + return "admin/article/edit.html"; + } + @GetRoute("login") public String login(Response response) { if (null != this.user()) { @@ -115,16 +120,16 @@ public String newPage(Request request) { return "admin/page_edit"; } - @GetRoute("page/:cid") - public String editPage(@PathParam String cid, Request request) { - Optional contents = contentsService.getContents(cid); - if (!contents.isPresent()) { - return render_404(); - } - request.attribute("contents", contents.get()); - request.attribute(Types.ATTACH_URL, Commons.site_option(Types.ATTACH_URL, Commons.site_url())); - return "admin/page_edit"; - } +// @GetRoute("page/:cid") +// public String editPage(@PathParam String cid, Request request) { +// Optional contents = contentsService.getContents(cid); +// if (!contents.isPresent()) { +// return render_404(); +// } +// request.attribute("contents", contents.get()); +// request.attribute(Types.ATTACH_URL, Commons.site_option(Types.ATTACH_URL, Commons.site_url())); +// return "admin/page_edit"; +// } @GetRoute("template") public String index(Request request) { diff --git a/src/main/java/com/tale/model/params/ArticleParam.java b/src/main/java/com/tale/model/params/ArticleParam.java index f7d0bf33..67a7cd30 100644 --- a/src/main/java/com/tale/model/params/ArticleParam.java +++ b/src/main/java/com/tale/model/params/ArticleParam.java @@ -11,4 +11,7 @@ @ToString(callSuper = true) public class ArticleParam extends PageParam { + private String type; + private String orderBy; + } diff --git a/src/main/java/com/tale/service/ContentsService.java b/src/main/java/com/tale/service/ContentsService.java index 27219ee8..2d7a4ca7 100644 --- a/src/main/java/com/tale/service/ContentsService.java +++ b/src/main/java/com/tale/service/ContentsService.java @@ -9,9 +9,12 @@ import com.tale.model.entity.Comments; import com.tale.model.entity.Contents; import com.tale.model.entity.Relationships; +import com.tale.model.params.ArticleParam; import com.tale.utils.TaleUtils; import com.vdurmont.emoji.EmojiParser; import io.github.biezhi.anima.Anima; +import io.github.biezhi.anima.core.AnimaQuery; +import io.github.biezhi.anima.enums.OrderBy; import io.github.biezhi.anima.page.Page; import java.util.Optional; @@ -37,15 +40,19 @@ public class ContentsService { * * @param id 唯一标识 */ - public Optional getContents(String id) { + public Contents getContents(String id) { + Contents contents = null; if (StringKit.isNotBlank(id)) { if (StringKit.isNumber(id)) { - return Optional.ofNullable(select().from(Contents.class).byId(id)); + contents = select().from(Contents.class).byId(id); } else { - return Optional.ofNullable(select().from(Contents.class).where(Contents::getSlug, id).one()); + contents = select().from(Contents.class).where(Contents::getSlug, id).one(); + } + if (null != contents) { + return this.mapContent(contents); } } - return Optional.empty(); + return contents; } /** @@ -107,12 +114,12 @@ public void updateArticle(Contents contents) { * @param cid 文章id */ public void delete(int cid) { - Optional contents = this.getContents(cid + ""); - contents.ifPresent(content -> { + Contents contents = this.getContents(cid + ""); + if (null != contents) { deleteById(Contents.class, cid); Anima.delete().from(Relationships.class).where(Relationships::getCid, cid).execute(); Anima.delete().from(Comments.class).where(Comments::getCid, cid).execute(); - }); + } } /** @@ -127,4 +134,16 @@ public Page getArticles(Integer mid, int page, int limit) { return select().bySQL(Contents.class, SQL_QUERY_ARTICLES, mid).page(page, limit); } + public Page findArticles(ArticleParam articleParam) { + AnimaQuery query = select().from(Contents.class); + query.where(Contents::getType, articleParam.getType()); + query.order(articleParam.getOrderBy()); + Page articles = query.page(articleParam.getPage(), articleParam.getLimit()); + return articles.map(this::mapContent); + } + + private Contents mapContent(Contents contents) { + contents.setContent(contents.getContent().replaceAll("\\\\\"", "\\\"")); + return contents; + } } diff --git a/src/main/resources/static/admin/js/edit_article.js b/src/main/resources/static/admin/js/edit_article.js new file mode 100644 index 00000000..289b3fc1 --- /dev/null +++ b/src/main/resources/static/admin/js/edit_article.js @@ -0,0 +1,330 @@ +var mditor, htmlEditor; +var tale = new $.tale(); +var attach_url = $('#attach_url').val(); +// 每60秒自动保存一次草稿 +var refreshIntervalId; +Dropzone.autoDiscover = false; + +var vm = new Vue({ + el: '#app', + data: { + article: { + cid: '', + title: '', + slug: '', + tags: '', + content: '', + status: 'draft', + fmtType: 'markdown', + allowComment: true, + allowPing: true, + allowFeed: true, + created: 0, + createTime: moment().format('YYYY-MM-DD HH:mm:ss'), + selected: ['默认分类'] + }, + categories: [] + }, + watch: { + article: { + createTime: function (val, oldVal) { + alert('val: ' + val); + } + } + }, + mounted: function () { + var $vm = this; + $vm.load(); + refreshIntervalId = setInterval("vm.autoSave()", 10 * 1000); + }, + methods: { + load: function () { + var $vm = this; + var pos = window.location.toString().lastIndexOf("/"); + var cid = window.location.toString().substring(pos + 1) + axios.all([ + axios.get('/admin/api/categories'), + axios.get('/admin/api/articles/' + cid + '') + ]).then(axios.spread(function (categoryResp, articleResp) { + $vm.categories = categoryResp.data.payload; + $vm.article = articleResp.data.payload; + })); + }, + autoSave: function () { + var $vm = this; + var content = $vm.article.fmtType === 'markdown' ? mditor.value : htmlEditor.summernote('code'); + if ($vm.article.title !== '' && content !== '') { + $('#content-editor').val(content); + + $vm.article.content = content; + $vm.article.categories = $vm.article.selected.join(','); + var params = tale.copy($vm.article); + params.selected = null; + + tale.post({ + url: '/admin/api/article/update', + data: params, + success: function (result) { + if (result && result.success) { + $vm.article.cid = result.payload; + } else { + tale.alertError(result.msg || '保存文章失败'); + } + }, + error: function () { + clearInterval(refreshIntervalId); + } + }); + } + }, + switchEditor: function (event) { + var type = this.article.fmtType; + var this_ = event.target; + if (type === 'markdown') { + // 切换为富文本编辑器 + if ($('#md-container .markdown-body').html().length > 0) { + $('#html-container .note-editable').empty().html($('#md-container .markdown-body').html()); + $('#html-container .note-placeholder').hide(); + } + mditor.value = ''; + $('#md-container').hide(); + $('#html-container').show(); + + this_.innerHTML = '切换为Markdown编辑器'; + + this.article.fmtType = 'html'; + } else { + // 切换为markdown编辑器 + if ($('#html-container .note-editable').html().length > 0) { + mditor.value = ''; + mditor.value = toMarkdown($('#html-container .note-editable').html()); + } + $('#html-container').hide(); + $('#md-container').show(); + + this.article.fmtType = 'markdown'; + + this_.innerHTML = '切换为富文本编辑器'; + htmlEditor.summernote("code", ""); + } + }, + publish: function (status) { + var $vm = this; + var content = this.article.fmtType === 'markdown' ? mditor.value : htmlEditor.summernote('code'); + var title = $vm.article.title; + if (title === '') { + tale.alertWarn('请输入文章标题'); + return; + } + if (content === '') { + tale.alertWarn('请输入文章内容'); + return; + } + clearInterval(refreshIntervalId); + + $vm.article.content = content; + $vm.article.status = status; + $vm.article.categories = $vm.article.selected.join(','); + + var params = tale.copy($vm.article); + params.selected = null; + + tale.post({ + url: '/admin/api/article/update', + data: params, + success: function (result) { + if (result && result.success) { + tale.alertOk({ + text: '文章保存成功', + then: function () { + setTimeout(function () { + window.location.href = '/admin/articles'; + }, 500); + } + }); + } else { + tale.alertError(result.msg || '保存文章失败'); + } + } + }); + } + } +}); + +$(document).ready(function () { + + $("#form_datetime").datetimepicker({ + format: 'yyyy-mm-dd hh:ii', + autoclose: true, + todayBtn: true, + weekStart: 1, + language: 'zh-CN' + }); + + mditor = window.mditor = Mditor.fromTextarea(document.getElementById('md-editor')); + // 富文本编辑器 + htmlEditor = $('.summernote').summernote({ + lang: 'zh-CN', + height: 340, + placeholder: '写点儿什么吧...', + //上传图片的接口 + callbacks: { + onImageUpload: function (files) { + var data = new FormData(); + data.append('image_up', files[0]); + tale.showLoading(); + $.ajax({ + url: '/admin/attach/upload', //上传图片请求的路径 + method: 'POST', //方法 + data: data, //数据 + processData: false, //告诉jQuery不要加工数据 + dataType: 'json', + contentType: false, // 告诉jQuery,在request head里不要设置Content-Type + success: function (result) { + tale.hideLoading(); + if (result && result.success) { + var url = $('#attach_url').val() + result.payload[0].fkey; + console.log('url =>' + url); + htmlEditor.summernote('insertImage', url); + } else { + tale.alertError(result.msg || '图片上传失败'); + } + } + }); + } + } + }); + + /* + * 切换编辑器 + * */ + $('#switch-btn').click(function () { + var type = $('#fmtType').val(); + var this_ = $(this); + if (type == 'markdown') { + // 切换为富文本编辑器 + if ($('#md-container .markdown-body').html().length > 0) { + $('#html-container .note-editable').empty().html($('#md-container .markdown-body').html()); + $('#html-container .note-placeholder').hide(); + + } + mditor.value = ''; + $('#md-container').hide(); + $('#html-container').show(); + this_.text('切换为Markdown编辑器'); + $('#fmtType').val('html'); + } else { + // 切换为markdown编辑器 + if ($('#html-container .note-editable').html().length > 0) { + mditor.value = ''; + mditor.value = toMarkdown($('#html-container .note-editable').html()); + } + $('#html-container').hide(); + $('#md-container').show(); + $('#fmtType').val('markdown'); + this_.text('切换为富文本编辑器'); + htmlEditor.summernote("code", ""); + } + }); + // Tags Input + $('#tags').tagsInput({ + width: '100%', + height: '35px', + defaultText: '请输入文章标签' + }); + + $('.toggle').toggles({ + on: true, + text: { + on: '开启', + off: '关闭' + } + }); + + $('#allowComment').toggleClass('disabled', false); + + $('#allowComment').on('toggle', function (e, active) { + vm.article.allowComment = active; + }); + + $('#allowPing').on('toggle', function (e, active) { + vm.article.allowPing = active; + }); + + $('#allowFeed').on('toggle', function (e, active) { + vm.article.allowFeed = active; + }); + + $('#addThumb').on('toggle', function (e, active) { + if (active) { + $('#dropzone-container').addClass('hide'); + $('#thumbImg').val(''); + } else { + $('#dropzone-container').removeClass('hide'); + $('#dropzone-container').show(); + } + }); + + $("#multiple-sel").select2({ + width: '100%' + }); + + if ($('#thumb-toggle').attr('thumb_url') != '') { + $('#thumb-toggle').toggles({ + on: true, + text: { + on: '开启', + off: '关闭' + } + }); + $('#thumb-toggle').attr('on', 'true'); + $('#dropzone').css('background-image', 'url(' + $('#thumb-container').attr('thumb_url') + ')'); + $('#dropzone').css('background-size', 'cover'); + $('#dropzone-container').show(); + } else { + $('#thumb-toggle').toggles({ + off: true, + text: { + on: '开启', + off: '关闭' + } + }); + $('#thumb-toggle').attr('on', 'false'); + $('#dropzone-container').hide(); + } + + var thumbdropzone = $('.dropzone'); + + // 缩略图上传 + $("#dropzone").dropzone({ + url: "/admin/attach/upload", + filesizeBase: 1024,//定义字节算法 默认1000 + maxFilesize: '10', //MB + fallback: function () { + tale.alertError('暂不支持您的浏览器上传!'); + }, + acceptedFiles: 'image/*', + dictFileTooBig: '您的文件超过10MB!', + dictInvalidInputType: '不支持您上传的类型', + init: function () { + this.on('success', function (files, result) { + console.log("upload success.."); + console.log(" result => " + result); + if (result && result.success) { + var url = attach_url + result.payload[0].fkey; + console.log('url => ' + url); + thumbdropzone.css('background-image', 'url(' + url + ')'); + thumbdropzone.css('background-size', 'cover'); + $('.dz-image').hide(); + $('#thumbImg').val(url); + } + }); + this.on('error', function (a, errorMessage, result) { + if (!result.success && result.msg) { + tale.alertError(result.msg || '缩略图上传失败'); + } + }); + } + }); + +}); diff --git a/src/main/resources/static/admin/js/article.js b/src/main/resources/static/admin/js/new_article.js similarity index 96% rename from src/main/resources/static/admin/js/article.js rename to src/main/resources/static/admin/js/new_article.js index 10ca934d..01fd7c60 100644 --- a/src/main/resources/static/admin/js/article.js +++ b/src/main/resources/static/admin/js/new_article.js @@ -25,6 +25,13 @@ var vm = new Vue({ }, categories: [] }, + watch: { + article: { + createTime: function (val, oldVal) { + alert('val: ' + val); + } + } + }, mounted: function () { var $vm = this; $vm.load(); @@ -34,7 +41,7 @@ var vm = new Vue({ load: function () { var $vm = this; tale.get({ - url: '/admin/categories.json', + url: '/admin/api/categories', success: function (data) { $vm.categories = data.payload }, @@ -55,7 +62,7 @@ var vm = new Vue({ var params = tale.copy($vm.article); params.selected = null; - var url = $vm.article.cid !== '' ? '/admin/article/update.json' : '/admin/article/new.json'; + var url = $vm.article.cid !== '' ? '/admin/api/article/update' : '/admin/api/article/new'; tale.post({ url: url, data: params, @@ -124,7 +131,7 @@ var vm = new Vue({ var params = tale.copy($vm.article); params.selected = null; - var url = $vm.article.cid !== '' ? '/admin/article/update.json' : '/admin/article/new.json'; + var url = $vm.article.cid !== '' ? '/admin/api/article/update' : '/admin/api/article/new'; tale.post({ url: url, data: params, diff --git a/src/main/resources/templates/admin/article/edit.html b/src/main/resources/templates/admin/article/edit.html new file mode 100644 index 00000000..90aa3d65 --- /dev/null +++ b/src/main/resources/templates/admin/article/edit.html @@ -0,0 +1,205 @@ +#include('../header.html',{active: 'publish', title:'编辑文章'}) + + + + +#call cdn_url('article_edit_head') + +
+
+

+ #if(null != contents) + 编辑文章 + #else + 发布文章 + #end +

+
+
+ + + +
+ + + + + + + + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+

可以为你的文章添加一张缩略图 ;)

+
+
+ +
+ +
+ +
+
+ +
+ +
+
+
+ +
+
+ 返回列表 + + +
+
+
+
+
+#include('../footer.html') + + + + +#call cdn_url('article_edit_foot') + + + \ No newline at end of file diff --git a/src/main/resources/templates/admin/article/new.html b/src/main/resources/templates/admin/article/new.html index ecb59b40..7a4ebef8 100644 --- a/src/main/resources/templates/admin/article/new.html +++ b/src/main/resources/templates/admin/article/new.html @@ -200,6 +200,6 @@

#call cdn_url('article_edit_foot') - + \ No newline at end of file diff --git a/src/main/resources/templates/admin/article_edit.html b/src/main/resources/templates/admin/article_edit.html index b8f7271b..f38337bf 100644 --- a/src/main/resources/templates/admin/article_edit.html +++ b/src/main/resources/templates/admin/article_edit.html @@ -221,6 +221,6 @@

#call cdn_url('article_edit_foot') - + \ No newline at end of file diff --git a/src/main/resources/templates/admin/articles.html b/src/main/resources/templates/admin/articles.html index 3ed06fc7..bbf264bd 100644 --- a/src/main/resources/templates/admin/articles.html +++ b/src/main/resources/templates/admin/articles.html @@ -16,30 +16,33 @@

文章管理

- - - {{ item.title }} - - {{ item.created | formatUnix }} - {{ item.hits }} - {{ item.categories }} - - 已发布 - 草稿 - - - - 编辑 - - - 删除 - - - 预览 - - - + + + {{ item.title }} + + {{ item.created | formatUnix }} + {{ item.hits }} + {{ item.categories }} + + 已发布 + 草稿 + + + + 编辑 + + + 删除 + + + 预览 + + + @@ -55,15 +58,12 @@

文章管理

}, mounted: function () { this.load(); - }, - filters: { - }, methods: { load: function () { var $vm = this; tale.get({ - url: '/admin/articles.json', + url: '/admin/api/articles', success: function (data) { $vm.articlePage = data.payload }, @@ -76,13 +76,12 @@

文章管理

deleteArticle: function (cid) { var $vm = this; tale.alertConfirm({ - title:'确定删除该文章吗?', + title: '确定删除该文章吗?', then: function () { tale.post({ - url : '/admin/article/delete', - data: {cid: cid}, + url: '/admin/api/article/delete/' + cid, success: function (result) { - if(result && result.success){ + if (result && result.success) { tale.alertOk('文章删除成功'); $vm.load(); } else {