/** * AKSRT Page Edit — 前台编辑增强 * * 提供前台编辑页面的增强功能: * - 自动保存草稿 * - 字数统计 * - 标签建议 * - 快捷键支持 * * @package AKSRT */ (function () { 'use strict'; var DRAFT_KEY_PREFIX = 'aksrt_draft_'; /** * 初始化页面编辑增强 * * @param {Object} options 配置项 */ function PageEdit(options) { this.options = Object.assign({ formSelector: 'form[data-aksrt-edit]', titleSelector: 'input[name="title"], input[name="topic_title"]', contentSelector: 'textarea[name="content"], textarea[name="topic_content"]', tagsSelector: 'input[name="tags"], input[name="topic_tags"]', autoSaveInterval: 30000, // 30秒 maxTitleLength: 200, maxContentLength: 100000, draftKey: '', }, options || {}); this.form = document.querySelector(this.options.formSelector); if (!this.form) return; this.autoSaveTimer = null; this.lastSavedContent = ''; this.init(); } PageEdit.prototype.init = function () { var self = this; this.titleInput = this.form.querySelector(this.options.titleSelector); this.contentTextarea = this.form.querySelector(this.options.contentSelector); this.tagsInput = this.form.querySelector(this.options.tagsSelector); // 字数统计 this.initWordCount(); // 自动保存草稿 this.initAutoSave(); // 快捷键 this.initShortcuts(); // 标签输入增强 this.initTagInput(); // 表单提交时清除草稿 this.form.addEventListener('submit', function () { self.clearDraft(); }); // 页面离开提醒 window.addEventListener('beforeunload', function (e) { if (self.hasUnsavedChanges()) { e.preventDefault(); e.returnValue = '您有未保存的修改,确定要离开吗?'; } }); }; PageEdit.prototype.initWordCount = function () { var self = this; // 创建字数统计显示 this.wordCountEl = document.createElement('div'); this.wordCountEl.className = 'aksrt-page-edit__word-count'; if (this.titleInput) { var titleCount = document.createElement('span'); titleCount.className = 'aksrt-page-edit__title-count'; this.titleInput.parentNode.appendChild(titleCount); this.titleInput.addEventListener('input', function () { var len = self.titleInput.value.length; titleCount.textContent = len + '/' + self.options.maxTitleLength; titleCount.classList.toggle('aksrt-page-edit__count--warn', len > self.options.maxTitleLength * 0.9); titleCount.classList.toggle('aksrt-page-edit__count--error', len > self.options.maxTitleLength); }); } if (this.contentTextarea) { this.contentTextarea.parentNode.appendChild(this.wordCountEl); this.updateWordCount(); this.contentTextarea.addEventListener('input', function () { self.updateWordCount(); }); } }; PageEdit.prototype.updateWordCount = function () { if (!this.contentTextarea || !this.wordCountEl) return; var text = this.contentTextarea.value; var chars = text.length; var words = text.trim() ? text.trim().split(/\s+/).length : 0; var chinese = (text.match(/[一-鿿]/g) || []).length; this.wordCountEl.textContent = '字数: ' + chars + ' | 词数: ' + words + ' | 中文: ' + chinese; this.wordCountEl.classList.toggle('aksrt-page-edit__count--warn', chars > this.options.maxContentLength * 0.9); this.wordCountEl.classList.toggle('aksrt-page-edit__count--error', chars > this.options.maxContentLength); }; PageEdit.prototype.initAutoSave = function () { var self = this; if (!this.options.draftKey) { // 从页面 URL 生成 draft key this.options.draftKey = DRAFT_KEY_PREFIX + (window.location.pathname.replace(/\//g, '_') || 'page'); } // 恢复草稿 this.restoreDraft(); // 定时自动保存 if (this.options.autoSaveInterval > 0) { this.autoSaveTimer = setInterval(function () { self.saveDraft(); }, this.options.autoSaveInterval); } }; PageEdit.prototype.saveDraft = function () { if (!this.form) return; var data = {}; if (this.titleInput) data.title = this.titleInput.value; if (this.contentTextarea) data.content = this.contentTextarea.value; if (this.tagsInput) data.tags = this.tagsInput.value; var content = JSON.stringify(data); if (content === this.lastSavedContent) return; try { localStorage.setItem(this.options.draftKey, content); this.lastSavedContent = content; // 显示保存提示 var toast = document.getElementById('aksrt-draft-toast'); if (!toast) { toast = document.createElement('div'); toast.id = 'aksrt-draft-toast'; toast.className = 'aksrt-page-edit__toast'; document.body.appendChild(toast); } toast.textContent = '草稿已自动保存'; toast.classList.add('aksrt-page-edit__toast--visible'); setTimeout(function () { toast.classList.remove('aksrt-page-edit__toast--visible'); }, 2000); } catch (e) { console.error('[AKSRT PageEdit] 保存草稿失败:', e); } }; PageEdit.prototype.restoreDraft = function () { try { var saved = localStorage.getItem(this.options.draftKey); if (!saved) return; var data = JSON.parse(saved); var hasContent = false; if (this.titleInput && data.title && !this.titleInput.value) { this.titleInput.value = data.title; hasContent = true; } if (this.contentTextarea && data.content && !this.contentTextarea.value) { this.contentTextarea.value = data.content; hasContent = true; } if (this.tagsInput && data.tags && !this.tagsInput.value) { this.tagsInput.value = data.tags; hasContent = true; } if (hasContent) { this.lastSavedContent = saved; } } catch (e) { console.error('[AKSRT PageEdit] 恢复草稿失败:', e); } }; PageEdit.prototype.clearDraft = function () { try { localStorage.removeItem(this.options.draftKey); } catch (e) {} }; PageEdit.prototype.hasUnsavedChanges = function () { if (!this.form) return false; var data = {}; if (this.titleInput) data.title = this.titleInput.value; if (this.contentTextarea) data.content = this.contentTextarea.value; if (this.tagsInput) data.tags = this.tagsInput.value; return JSON.stringify(data) !== this.lastSavedContent; }; PageEdit.prototype.initShortcuts = function () { var self = this; this.form.addEventListener('keydown', function (e) { // Ctrl+S / Cmd+S 保存草稿 if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); self.saveDraft(); } }); }; PageEdit.prototype.initTagInput = function () { if (!this.tagsInput) return; // 回车键添加标签 this.tagsInput.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); } }); }; // 自动初始化 function autoInit() { var form = document.querySelector('form[data-aksrt-edit]'); if (form) { new PageEdit(); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', autoInit); } else { autoInit(); } window.AksrtPageEdit = PageEdit; })();