diff --git a/assets/js/front-submit.js b/assets/js/front-submit.js index 56d29b8b..291cce5b 100644 --- a/assets/js/front-submit.js +++ b/assets/js/front-submit.js @@ -4,6 +4,7 @@ * * @package AKSRT */ +/* global notyf */ (function () { 'use strict'; @@ -216,11 +217,20 @@ return; } this.isSubmitting = true; + var toastId = action === 'posts_draft' + ? 'aksrt-front-submit-draft-toast' + : 'aksrt-front-submit-publish-toast'; var original = btn.innerHTML; btn.disabled = true; btn.innerHTML = ' ' + (action === 'posts_draft' ? i18n.saving || 'Saving...' : i18n.submitting || 'Submitting...'); + notyf( + action === 'posts_draft' ? i18n.saving || '正在保存草稿…' : i18n.submitting || '正在提交文章…', + 'load', + '', + toastId + ); var payload = this.collectPayload(action); if (payload.featured_data && typeof payload.featured_data === 'object') { @@ -268,29 +278,32 @@ ''; } } - btn.innerHTML = - ' ' + - (d.message || (action === 'posts_draft' ? i18n.saved : i18n.submitted)); + notyf( + d.message || (action === 'posts_draft' ? i18n.saved || '草稿已保存' : i18n.submitted || '投稿已提交'), + 'success', + 5, + toastId + ); if (d.goto) { setTimeout(function () { window.location.href = d.goto; - }, 600); + }, 1000); return; } if (d.reload) { setTimeout(function () { window.location.reload(); - }, 600); + }, 1000); return; } setTimeout(function () { btn.disabled = false; btn.innerHTML = original; self.isSubmitting = false; - }, 1200); + }, 300); }) .catch(function (err) { - alert(err.message || i18n.error || 'Error'); + notyf(err.message || i18n.error || '操作失败,请重试', 'danger', 5, toastId); btn.disabled = false; btn.innerHTML = original; self.isSubmitting = false; @@ -330,6 +343,9 @@ if (!window.confirm(i18n.confirmDelete || 'Delete?')) { return; } + var toastId = 'aksrt-front-submit-delete-draft-toast'; + btn.disabled = true; + notyf(i18n.deleting || '正在删除草稿…', 'load', '', toastId); fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', @@ -345,9 +361,15 @@ if (item) { item.remove(); } + notyf((res.data && res.data.message) || i18n.deleted || '草稿已删除', 'success', 5, toastId); } else { - alert((res && res.data && res.data.message) || i18n.error || 'Error'); + btn.disabled = false; + notyf((res && res.data && res.data.message) || i18n.error || '删除失败', 'danger', 5, toastId); } + }) + .catch(function () { + btn.disabled = false; + notyf(i18n.error || '网络错误,请重试', 'danger', 5, toastId); }); }); }, @@ -420,7 +442,7 @@ return Promise.reject(new Error(i18n.error || 'Upload unavailable')); } if (upload.maxSize && file.size > upload.maxSize) { - return Promise.reject(new Error('File is too large')); + return Promise.reject(new Error(i18n.fileTooLarge || '文件大小超过限制')); } var body = new FormData(); body.append('file', file); @@ -469,21 +491,33 @@ return; } if (act === 'image') { + var imageToastId = 'aksrt-front-submit-featured-upload-toast'; self.pickUpload('image', 'image/*').then(function (file) { if (!file) return null; + notyf(i18n.uploading || '正在上传封面…', 'load', '', imageToastId); return self.uploadFile('image', file); }).then(function (file) { - if (file) self.setFeatured('image', { url: file.url, id: file.attachment_id }); - }).catch(function (error) { alert(error.message || i18n.error || 'Upload failed'); }); + if (!file) return; + self.setFeatured('image', { url: file.url, id: file.attachment_id }); + notyf(i18n.uploaded || '封面上传成功', 'success', 5, imageToastId); + }).catch(function (error) { + notyf(error.message || i18n.error || '封面上传失败', 'danger', 5, imageToastId); + }); return; } if (act === 'slide') { + var slideToastId = 'aksrt-front-submit-slide-upload-toast'; self.pickUpload('image', 'image/*').then(function (file) { if (!file) return null; + notyf(i18n.uploading || '正在上传幻灯片封面…', 'load', '', slideToastId); return self.uploadFile('image', file); }).then(function (file) { - if (file) self.setFeatured('slide', [{ url: file.url, id: file.attachment_id }]); - }).catch(function (error) { alert(error.message || i18n.error || 'Upload failed'); }); + if (!file) return; + self.setFeatured('slide', [{ url: file.url, id: file.attachment_id }]); + notyf(i18n.uploaded || '幻灯片封面上传成功', 'success', 5, slideToastId); + }).catch(function (error) { + notyf(error.message || i18n.error || '幻灯片封面上传失败', 'danger', 5, slideToastId); + }); return; } if (act === 'video') { @@ -491,12 +525,21 @@ if (!url) { return; } + var videoToastId = 'aksrt-front-submit-video-cover-toast'; self.pickUpload('image', 'image/*').then(function (file) { - if (!file) return null; + if (!file) { + self.setFeatured('video', { url: url, pic: '' }); + return null; + } + notyf(i18n.uploading || '正在上传视频封面…', 'load', '', videoToastId); return self.uploadFile('image', file); }).then(function (file) { - self.setFeatured('video', { url: url, pic: file ? file.url : '' }); - }).catch(function (error) { alert(error.message || i18n.error || 'Upload failed'); }); + if (!file) return; + self.setFeatured('video', { url: url, pic: file.url }); + notyf(i18n.uploaded || '视频封面上传成功', 'success', 5, videoToastId); + }).catch(function (error) { + notyf(error.message || i18n.error || '视频封面上传失败', 'danger', 5, videoToastId); + }); } }); }, @@ -561,9 +604,11 @@ var uploadBtn = qs('[data-pay-download-upload]', box); if (uploadBtn && list && tpl) { uploadBtn.addEventListener('click', function () { + var toastId = 'aksrt-front-submit-resource-upload-toast'; App.pickUpload('resource', '*/*').then(function (file) { if (!file) return null; uploadBtn.disabled = true; + notyf(i18n.uploading || '正在上传附件…', 'load', '', toastId); return App.uploadFile('resource', file); }).then(function (file) { if (!file) return; @@ -577,8 +622,9 @@ if (url) url.value = file.url || ''; if (label) label.value = file.name || ''; list.appendChild(node); + notyf(i18n.uploaded || '附件上传成功', 'success', 5, toastId); }).catch(function (error) { - alert(error.message || i18n.error || 'Upload failed'); + notyf(error.message || i18n.error || '附件上传失败', 'danger', 5, toastId); }).finally(function () { uploadBtn.disabled = false; }); @@ -606,12 +652,18 @@ } e.preventDefault(); if (btn.hasAttribute('data-insert-upload-image')) { + var toastId = 'aksrt-front-submit-editor-image-toast'; App.pickUpload('image', 'image/*').then(function (file) { if (!file) return null; + notyf(i18n.uploading || '正在上传正文图片…', 'load', '', toastId); return App.uploadFile('image', file); }).then(function (file) { - if (file && file.url) insertEditorContent(''); - }).catch(function (error) { alert(error.message || i18n.error || 'Upload failed'); }); + if (!file || !file.url) return; + insertEditorContent(''); + notyf(i18n.uploaded || '正文图片上传成功', 'success', 5, toastId); + }).catch(function (error) { + notyf(error.message || i18n.error || '正文图片上传失败', 'danger', 5, toastId); + }); return; } var type = btn.getAttribute('data-insert-shortcode'); @@ -638,12 +690,16 @@ var action = btn.getAttribute('data-ai-action'); var content = getEditorContent(); if (!content || content.replace(/<[^>]+>/g, '').trim().length < 10) { - alert(i18n.needContent || 'Need content'); + notyf(i18n.needContent || '请先填写正文内容', 'warning'); return; } + var toastId = action === 'tags' + ? 'aksrt-front-submit-ai-tags-toast' + : 'aksrt-front-submit-ai-summary-toast'; var old = btn.textContent; btn.disabled = true; btn.textContent = i18n.aiGenerating || '...'; + notyf(i18n.aiGenerating || '正在生成…', 'load', '', toastId); fetch((cfg.restUrl || '').replace(/\/$/, '') + '/ai/' + action, { method: 'POST', credentials: 'same-origin', @@ -672,9 +728,10 @@ tags.value = text; } } + notyf(i18n.aiFilled || '已填入,可继续编辑', 'success', 5, toastId); }) .catch(function (err) { - alert(err.message || i18n.aiFailed || 'AI failed'); + notyf(err.message || i18n.aiFailed || 'AI 生成失败', 'danger', 5, toastId); }) .finally(function () { btn.disabled = false; diff --git a/templates/pages/page-submit.php b/templates/pages/page-submit.php index d71998ae..6f63e99a 100644 --- a/templates/pages/page-submit.php +++ b/templates/pages/page-submit.php @@ -54,7 +54,7 @@ if (file_exists($js)) { wp_enqueue_script( 'aksrt-front-submit', get_template_directory_uri() . '/assets/js/front-submit.js', - ['jquery'], + ['jquery', 'aksrt-notyf'], (string) filemtime($js), true ); @@ -105,6 +105,11 @@ if (file_exists($js)) { 'aiFailed' => __('AI 生成失败', 'aksrt'), 'needContent' => __('请先填写正文内容', 'aksrt'), 'aiFilled' => __('已填入,可继续编辑', 'aksrt'), + 'deleting' => __('正在删除草稿…', 'aksrt'), + 'deleted' => __('草稿已删除', 'aksrt'), + 'uploading' => __('正在上传…', 'aksrt'), + 'uploaded' => __('上传成功', 'aksrt'), + 'fileTooLarge' => __('文件大小超过限制', 'aksrt'), 'lastSaved' => __('最后保存:', 'aksrt'), 'preview' => __('预览文章', 'aksrt'), 'pickImage' => __('选择图片', 'aksrt'), diff --git a/tests/widget-system/toast-contract.test.cjs b/tests/widget-system/toast-contract.test.cjs index e3d2d383..e6cb80a2 100644 --- a/tests/widget-system/toast-contract.test.cjs +++ b/tests/widget-system/toast-contract.test.cjs @@ -316,6 +316,45 @@ test('settings actions use rehydrated fixed-id notyf flows', () => { assert.equal(fs.existsSync(path.join(root, 'templates/parts/user-center/account-modals.php')), false); }); +test('front submit actions use direct fixed-id notyf flows', () => { + const submit = read('assets/js/front-submit.js'); + const page = read('templates/pages/page-submit.php'); + + [ + 'aksrt-front-submit-draft-toast', + 'aksrt-front-submit-publish-toast', + 'aksrt-front-submit-delete-draft-toast', + 'aksrt-front-submit-featured-upload-toast', + 'aksrt-front-submit-slide-upload-toast', + 'aksrt-front-submit-video-cover-toast', + 'aksrt-front-submit-resource-upload-toast', + 'aksrt-front-submit-editor-image-toast', + 'aksrt-front-submit-ai-tags-toast', + 'aksrt-front-submit-ai-summary-toast', + ].forEach((id) => assert.match(submit, new RegExp("['\"]" + id + "['\"]"))); + + assert.match(submit, /submit: function[\s\S]*?notyf\([^;]+?'load'[^;]+?toastId[\s\S]*?notyf\([^;]+?'success'[^;]+?toastId[\s\S]*?notyf\([^;]+?'danger'[^;]+?toastId/); + assert.match(submit, /bindDraftDelete: function[\s\S]*?'aksrt-front-submit-delete-draft-toast'[\s\S]*?'load'[\s\S]*?'success'[\s\S]*?'danger'/); + + [ + 'imageToastId', + 'slideToastId', + 'videoToastId', + ].forEach((variable) => { + assert.match(submit, new RegExp("notyf\\([^;]+?'load'[^;]+?" + variable + "\\);")); + assert.match(submit, new RegExp("notyf\\([^;]+?'success'[^;]+?" + variable + "\\);")); + assert.match(submit, new RegExp("notyf\\([^;]+?'danger'[^;]+?" + variable + "\\);")); + }); + + assert.match(submit, /aksrt-front-submit-resource-upload-toast[\s\S]*?'load'[\s\S]*?'success'[\s\S]*?'danger'/); + assert.match(submit, /aksrt-front-submit-editor-image-toast[\s\S]*?'load'[\s\S]*?'success'[\s\S]*?'danger'/); + assert.match(submit, /aksrt-front-submit-ai-tags-toast[\s\S]*?aksrt-front-submit-ai-summary-toast[\s\S]*?'load'[\s\S]*?'success'[\s\S]*?'danger'/); + assert.match(submit, /needContent[^;]+?'warning'/); + assert.doesNotMatch(submit, /\b(?:window\.)?alert\s*\(/); + + assert.match(page, /'aksrt-front-submit',[\s\S]*?'\/assets\/js\/front-submit\.js',[\s\S]*?\[[^\]]*'aksrt-notyf'[^\]]*\]/); +}); + test('legacy frontend toast APIs and selectors are fully removed', () => { const files = [ 'assets/js',