現在用在前端的編輯器還挺多的,我這裡使用的是TOAST UI Editor,支援一般所見即所得和markdown
官方給的文檔寫的挺清楚的,先在view/component創建editor.html寫入
{{ define "editor"}}
<div id="editorContainer">
<form id="NewBlogForm" style="display: block;">
<label for="NewBlogName">Blog Name</label>
<input id="NewBlogName" type="text" placeholder="限制100字(必填)">
<p class="invisible">超過100字</p>
<label for="NewBlogDescription">簡述</label>
<input id="NewBlogDescription" type="text" placeholder="能吸引人點進來看全文的簡短前言 (限制255字)">
<p class="invisible">超過255字</p>
<div class="input-group mb-3">
<div class="input-group-prepend">
<label class="input-group-text" for="inputGroupSelect01">類型</label>
</div>
<select id="TypeSelect">
<option value="1">Project</option>
<option value="2">Article</option>
</select>
</div>
<label for="editor">內文</label>
<div id="editor"></div>
<p class="invisible">內文不得為空</p>
<button id="EditorSubmitBtn" type="submit">Submit</button>
</form>
<!-- Local JS-->
<script src="http://asset.dcreater.com/static/js/editor.js"></script>
</div>
{{ end }}
在js創建editor.js寫入
let editor;
// option of blog type
let blogType;
// initial value of blog content
let ititialContent = "";
// check hashtag
HandleNewPost();
function HandleNewPost() {
ShowForm()
editor = CreateEditor();
}
// create a new editor
function CreateEditor() {
const {Editor} = toastui;
const {codeSyntaxHighlight, colorSyntax, tableMergedCell} = Editor.plugin;
return Editor.factory({
el: document.querySelector('#editor'),
usageStatistics: false,
height: '500px',
initialEditType: 'wysiwyg',
previewStyle: 'tab',
initialValue: ititialContent,
plugins: [codeSyntaxHighlight, colorSyntax, tableMergedCell],
})
}
// show blog or project form
function ShowForm() {
$("#editorContainer").show();
}
// prevent color picker reload page
$("#NewBlogForm").submit(function (e) {
e.preventDefault();
});
// choose type event
$('#TypeSelect').on('click', function () {
blogType = $(this).val();
});
解釋:
記得用cdn引入,載入網頁就能看到editor了,由於默認的圖片上傳toast ui會將他轉回base64直接存在文章,使的文章變得太大,所以我們要利用他提供的hook處理圖片上傳,稍微修改代碼
// create a new editor
function CreateEditor() {
const {Editor} = toastui;
const {codeSyntaxHighlight, colorSyntax, tableMergedCell} = Editor.plugin;
return Editor.factory({
el: document.querySelector('#editor'),
usageStatistics: false,
height: '500px',
initialEditType: 'wysiwyg',
previewStyle: 'tab',
initialValue: initialContent,
plugins: [codeSyntaxHighlight, colorSyntax, tableMergedCell],
hooks: {
'addImageBlobHook': imageUpload
}
})
}
// handle for editor upload image
function imageUpload(blob, callback) {
let data = new FormData();
let path = window.location.pathname.split("/").slice(1);
let oid = path.length > 1 ? meta.boid : meta.oid;
let name = geTimeNowStr();
let url = "http://asset.dcreater.com/img/" + oid + "/" + name;
data.append('content', blob);
data.append('fileName', name);
data.append('oid', oid);
$.ajax({
url: url,
data: data,
cache: false,
contentType: false,
processData: false,
method: 'POST',
mimeType: 'multipart/form-data',
xhrFields: {
withCredentials: true
},
success: function (data) {
// run callback
callback(url);
},
error: function (jqXHR, textStatus, errorThrown) {
alert("sorry, something error in upload image. Please try again later");
}
});
function geTimeNowStr() {
let date = new Date(Date.now());
return date.toISOString();
}
}
解釋:
hooks
在上傳圖片時會呼叫imageUpload
這個function這個editor有可能是寫新的文章,也有可能是修改文章,如果是修改,我們需要把原本blog的資料還原上來以供修改,我們這邊不註冊新的網址路徑,直接單頁定義"#NewBlog", "#UpdateBlog"這兩個錨點來區別
// create a new blog or update exist blog
$("#EditorSubmitBtn").on("click", function (e) {
let blogType = {"1": "project", "2": "article"};
let name = $("#NewBlogName").val();
let newName = (name === meta.bname ? "" : name);
let description = $("#NewBlogDescription").val();
let type = blogType[$("#TypeSelect").val()];
let content = new Blob([editor.getMarkdown()], {type: 'text/plain'});
let url = window.location.pathname + (window.location.hash === "#UpdateBlog" ? "" : "/" + name);
let path = window.location.pathname.split("/").slice(1);
let oid = path.length > 1 ? meta.boid : meta.oid;
let method = 'POST';
let newSuperPath = GetSuperURL(path);
let superid = (window.location.hash === "#UpdateBlog" ? meta.bsuper : (path.length > 1 ? meta.bid : 0));
let data = new FormData();
data.append('descript', description);
data.append('blogType', type);
data.append("superid", superid);
data.append("oid", oid);
if (type === "article") {
data.append("content", content);
}
if (window.location.hash === "#UpdateBlog") {
// update
data.append("bid", meta.bid);
data.append("newsuperid", -1);
data.append("newsuperUrl", newSuperPath);
data.append("newname", newName);
method = 'put';
}
$.ajax({
url: url,
data: data,
cache: false,
contentType: false,
processData: false,
method: method,
mimeType: 'multipart/form-data',
withCredentials: true,
success: function (data) {
// redirect
window.location.href = window.location.hash === "#UpdateBlog" ? newSuperPath + "/" + (newName === "" ? meta.bname : newName) : url;
},
error: function (jqXHR, textStatus, errorThrown) {
alert("sorry, something error in create blog. Please try again later");
}
});
e.preventDefault();
});
// show blog or project form
function ShowForm() {
$("#editorContainer").show();
FillEditor();
}
// fill form of update blog
function FillEditor() {
let content = $('#content');
if (content.length > 0) {
$("#NewBlogName").val(meta.bname);
$("#NewBlogDescription").val(meta.bdescription);
$("#TypeSelect").val(meta.btype).prop('disabled', true);
initialContent = $("#metaContent").html();
}
}
最後來處裡editor的出現與隱藏,只有updata與發新聞張需要顯示,平常顯示正常blog內容就好
function HandleNewPost() {
// editflag that create editor
let editFlag = ["#NewBlog", "#UpdateBlog"];
if (editFlag.includes(window.location.hash)) {
HandleShowForm();
editor = ShowEditor();
} else {
HandleHideForm();
HideEditor();
}
}
// hashtag event
$(window).on('hashchange', function () {
HandleNewPost();
});
function HandleShowForm() {
HideBlogList();
ShowReturnBtn();
HideNewBlogBtn();
ShowForm();
}
function HandleHideForm() {
ShowBlogList();
HideReturnBtn();
ShowNewBlogBtn();
HideForm();
}
// Hide blog or project form
function HideForm() {
$("#editorContainer").hide();
}
function HideEditor() {
$('#editor').children().remove();
}
function ShowEditor() {
return CreateEditor();
}
function HideBlogList() {
$("main.container").hide();
}
function ShowBlogList() {
$("main.container").show();
}
function ShowReturnBtn() {
$("#ReturnNav").show();
}
function HideReturnBtn() {
$("#ReturnNav").hide();
}
function ShowNewBlogBtn() {
if (meta.btype === 1) {
$("#NewBlogNav").show();
} else {
$("#NewBlogNav").hide();
}
$("#UpdateBlogNav").show();
}
function HideNewBlogBtn() {
$("#NewBlogNav").hide();
$("#UpdateBlogNav").hide();
}
這邊就可以看出jquery的劣勢,要做單頁應用哪些出現哪些消失控制得很繁瑣,vue之類的會叫為簡化
現在回來處理blog內容的顯示,把使用者寫的markdown parse成html,打開main.js補上
if (window.location.pathname === "/") {
rend_root();
} else {
rend_blog();
rend_content();
}
// rend for blog content
function rend_content() {
let content = $('#content');
if (content.length <= 0) {
return;
}
let context = content.html();
const { Editor } = toastui;
const { codeSyntaxHighlight, tableMergedCell } = Editor.plugin;
// create viewer
const viewer = Editor.factory({
el: document.querySelector('#content'),
viewer: true,
height: '500px',
initialValue: context,
plugins: [codeSyntaxHighlight, tableMergedCell]
});
// hide origin
content.after("<div id='metaContent' style='display:none'>" + context + "</div>");
}
前端就這樣告一段落,有時間會把其他功能補上,基本上寫code就告一段落,之後會寫部屬相關的工具與服務
目前的工作環境
.
├── app
│ ├── apperr
│ │ ├── error.go
│ │ └── handle.go
│ ├── common
│ │ └── cookie.go
│ ├── config
│ │ └── app
│ │ ├── app.yaml
│ │ └── error.yaml
│ ├── database
│ │ ├── auth.go
│ │ ├── connect.go
│ │ ├── error.go
│ │ ├── main.go
│ │ └── scheme.go
│ ├── go.mod
│ ├── go.sum
│ ├── log
│ │ ├── logger.go
│ │ └── logging.go
│ ├── main.go
│ ├── middleware
│ │ ├── auth.go
│ │ ├── error.go
│ │ └── log.go
│ ├── router
│ │ ├── account.go
│ │ ├── asset.go
│ │ ├── host_switch.go
│ │ └── main.go
│ ├── serve
│ │ ├── account.go
│ │ ├── asset.go
│ │ ├── auth.go
│ │ ├── main.go
│ │ └── main_test.go
│ ├── setting
│ │ └── setting.go
│ ├── util
│ │ ├── debug
│ │ │ ├── stack.go
│ │ │ └── stack_test.go
│ │ ├── file
│ │ │ └── file.go
│ │ ├── hash
│ │ │ ├── hash.go
│ │ │ └── hash_test.go
│ │ └── random
│ │ └── random.go
│ └── view
│ ├── css
│ ├── html
│ │ ├── component
│ │ │ ├── blogContainer.html
│ │ │ ├── blogList.html
│ │ │ ├── editor.html
│ │ │ ├── navigation.html
│ │ │ └── signContainer.html
│ │ └── meta
│ │ ├── head.html
│ │ └── index.html
│ └── js
│ ├── account.js
│ ├── editor.js
│ ├── main.js
│ ├── owner.js
│ └── sign.js
└── database
└── maindata