Files
go_blog/templates/user/my_article_form.html
T
dsh 6216b9af13 feat: 文章新建/编辑页编辑器支持本地图片上传并插入正文
- 两套页面(admin + 普通用户)EasyMDE 的 image 按钮改为“上传图片”:
  选择本地图片 → 走既有附件接口(files 表 type=attachments,新建页
  session_token / 编辑页 article_id)→ 在正文光标处插入 ![](url)
- templates/layouts/base.html:新增共享 window.blogUploadImage 上传助手
- templates/user/my_article_form.html:新建页 session_token 输入框加 id
  (编辑器上传需要读取);补齐原本缺失的上传能力
- i18n:新增 article_image_upload / article_image_not_image(中英)
- 服务端依既有上传策略校验(扩展名/MIME/魔数/大小),客户端限 image/*
2026-08-28 20:02:46 +08:00

147 lines
7.2 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "my_article_form"}}
{{template "header" .}}
{{template "markdown_assets" .}}
<section class="max-w-4xl mx-auto px-4 py-12">
<div class="mb-8">
<h2 class="text-3xl font-bold text-gray-900">{{.FormTitleText}}</h2>
</div>
<div id="myArticleError" class="mb-6 bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg {{if not .Error}}hidden{{end}}">
{{.Error}}
</div>
<form id="myArticleForm" action="{{.FormAction}}" method="post" class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 space-y-6">
<input type="hidden" name="_csrf" value="{{.CSRFToken}}">
{{if .SessionToken}}
<input type="hidden" name="session_token" id="myArticleSessionToken" value="{{.SessionToken}}">
{{end}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">{{index .Tr "article_field_title"}}</label>
<input type="text" name="title" value="{{.FormTitle}}" required
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">{{index .Tr "article_field_slug"}}</label>
<input type="text" name="slug" value="{{.FormSlug}}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<p class="mt-1 text-sm text-gray-500">{{index .Tr "article_slug_help"}}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">{{index .Tr "article_field_summary"}}</label>
<textarea name="summary" rows="3"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">{{.FormSummary}}</textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">{{index .Tr "article_field_content"}}</label>
<textarea id="content" name="content" rows="20"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">{{.FormContent}}</textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">{{index .Tr "article_field_cover"}}</label>
<input type="text" name="cover" value="{{.FormCover}}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<p class="mt-1 text-sm text-gray-500">{{index .Tr "article_cover_help"}}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">{{index .Tr "article_published_at"}}</label>
<input type="datetime-local" name="published_at" value="{{.FormPublishedAt}}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<p class="mt-1 text-sm text-gray-500">{{index .Tr "article_published_at_hint"}}</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">{{index .Tr "article_field_status"}}</label>
<select name="status"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="0" {{if eq .FormStatus "0"}}selected{{end}}>{{index .Tr "article_draft"}}</option>
<option value="1" {{if eq .FormStatus "1"}}selected{{end}}>{{index .Tr "article_published"}}</option>
</select>
</div>
</div>
<div class="flex gap-3">
<button type="submit"
class="bg-blue-600 text-white px-6 py-2 rounded-lg font-medium hover:bg-blue-700 transition-colors">
{{index .Tr "article_save"}}
</button>
<a href="/my/articles"
class="bg-gray-200 text-gray-700 px-6 py-2 rounded-lg font-medium hover:bg-gray-300 transition-colors">
{{index .Tr "article_cancel"}}
</a>
</div>
</form>
</section>
<script>
var myEasyMDE = null;
document.addEventListener('DOMContentLoaded', function() {
// 文章附件接口的上下文:新建页用 session_token,编辑页用 article_id。
var sessEl = document.getElementById('myArticleSessionToken');
var ATT = {
articleID: {{ if .FormArticleID }}{{ .FormArticleID }}{{ else }}0{{ end }},
sessionToken: sessEl ? sessEl.value : '',
uploadURL: "/api/my/articles/attachments"
};
// 编辑器“上传图片”按钮:选择本地图片 → 上传(files 表)→ 插入正文光标处。
function uploadImageAction(editor) {
var input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.onchange = function () {
var f = input.files && input.files[0];
if (!f) return;
blogUploadImage({ url: ATT.uploadURL, file: f, articleID: ATT.articleID, sessionToken: ATT.sessionToken })
.then(function (r) {
if (r.error) { blogShowError('myArticleError', r.error); return; }
if (!r.is_image) { blogShowError('myArticleError', '{{index .Tr "article_image_not_image"}}'); return; }
editor.codemirror.replaceSelection('![' + r.filename + '](' + r.url + ')\n');
editor.codemirror.focus();
});
};
input.click();
}
myEasyMDE = new EasyMDE({
element: document.getElementById('content'),
autoDownloadFontAwesome: false,
spellChecker: false,
status: false,
previewRender: function (plainText, preview) {
return '<div class="md-body">' + BlogMD.render(plainText) + '</div>';
},
toolbar: ["bold", "italic", "heading", "|", "quote", "unordered-list", "ordered-list", "|",
"link", { name: 'uploadImage', className: 'fa fa-image', title: '{{index .Tr "article_image_upload"}}', action: uploadImageAction }, "|", "preview", "side-by-side", "fullscreen", "|", "guide"]
});
});
// ---- Form submitJSON API ----
(function () {
var form = document.getElementById('myArticleForm');
if (!form) return;
var articleId = {{ if .FormArticleID }}{{ .FormArticleID }}{{ else }}0{{ end }};
form.addEventListener('submit', function (e) {
e.preventDefault();
var btn = e.submitter || null;
var ta = document.getElementById('content');
if (ta && myEasyMDE) { ta.value = myEasyMDE.value(); }
var method = articleId ? 'PUT' : 'POST';
var url = articleId ? '/api/my/articles/' + articleId : '/api/my/articles';
blogAPI(method, url, blogForm(form, btn)).then(function (r) {
if (r.ok) { window.location.href = r.redirect || '/my/articles'; }
else { blogShowError('myArticleError', r.error || 'Failed to save article.'); }
});
});
})();
</script>
{{template "footer" .}}
{{end}}