Files
wordpress-theme-aksrt/assets/js/admin-ai-seo.js
T
kate522andClaude Opus 5 35bb96e82f refactor: rename the theme brand and every identifier from AKSRT to Jio
Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.

Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
  cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
  window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)

Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
  so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
  (dropped 3 stale entries, added the 17 missing ones)

Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
  get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
  when it is empty

Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.

Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-01 20:05:04 +08:00

288 lines
9.0 KiB
JavaScript
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.
/**
* Jio Admin AI SEO — SEO generate + summary → excerpt
*/
(function () {
'use strict';
if (!window.jioAiSeo) return;
var CFG = window.jioAiSeo;
var REST = (CFG.restUrl || '/wp-json/jio/v1').replace(/\/$/, '');
var NONCE = CFG.nonce || '';
var I18N = CFG.i18n || {};
function notify($btn, msg, type) {
var wrap = $btn.closest('.jio-seo-meta-box, td, .form-field, p, .jio-ai-summary-row') || $btn.parentNode;
if (!wrap) return;
var old = wrap.querySelector('.jio-ai-seo-msg');
if (old) old.remove();
var el = document.createElement('span');
el.className = 'jio-ai-seo-msg ' + (type === 'error' ? 'is-error' : 'is-ok');
el.textContent = msg;
wrap.appendChild(el);
setTimeout(function () {
if (el.parentNode) el.parentNode.removeChild(el);
}, 8000);
}
function fillPost(data) {
var map = {
title: 'jio_seo_title',
keywords: 'jio_seo_keywords',
description: 'jio_seo_description',
};
Object.keys(map).forEach(function (key) {
var field = data[key] || {};
if (!field.content) return;
var input = document.getElementById(map[key]) || document.querySelector('[name="' + map[key] + '"]');
if (input) input.value = field.content;
});
}
function fillTerm(data) {
var map = {
title: '.jio-term-seo-title, #jio_seo_title',
keywords: '.jio-term-seo-keywords, #jio_seo_keywords',
description: '.jio-term-seo-description, #jio_seo_description',
};
Object.keys(map).forEach(function (key) {
var field = data[key] || {};
if (!field.content) return;
var input = document.querySelector(map[key]);
if (input) input.value = field.content;
});
}
function collectErrors(data) {
var errors = [];
['title', 'keywords', 'description'].forEach(function (key) {
if (data[key] && data[key].error) errors.push(data[key].error);
});
return errors;
}
function getEditorTitle() {
var titleInput = document.getElementById('title');
if (titleInput && titleInput.value) return titleInput.value.trim();
// Block editor
try {
if (window.wp && wp.data && wp.data.select) {
var ed = wp.data.select('core/editor');
if (ed && ed.getEditedPostAttribute) {
return String(ed.getEditedPostAttribute('title') || '').trim();
}
}
} catch (e) { /* ignore */ }
return '';
}
function getEditorContent() {
var content = '';
// Block editor first (post-new.php uses Gutenberg).
try {
if (window.wp && wp.data && wp.data.select) {
var ed = wp.data.select('core/editor');
if (ed && ed.getEditedPostContent) {
var blockContent = ed.getEditedPostContent();
if (blockContent) content = blockContent;
}
}
} catch (e) { /* ignore */ }
if (!content) {
var ta = document.getElementById('content');
if (ta && ta.value) content = ta.value;
}
// TinyMCE classic.
try {
if (!content && window.tinymce && tinymce.get) {
var editor = tinymce.get('content');
if (editor && editor.getContent) {
content = editor.getContent({ format: 'text' }) || editor.getContent() || '';
}
}
} catch (e2) { /* ignore */ }
// Strip tags for API plain-text summary.
var div = document.createElement('div');
div.innerHTML = content || '';
var plain = (div.textContent || div.innerText || content || '').replace(/\u00a0/g, ' ').trim();
return plain;
}
function setExcerptValue(summary) {
if (!summary) return false;
// Classic: #excerpt
var classic = document.getElementById('excerpt');
if (classic) {
classic.value = summary;
classic.dispatchEvent(new Event('input', { bubbles: true }));
classic.dispatchEvent(new Event('change', { bubbles: true }));
return true;
}
// Block editor excerpt attribute
try {
if (window.wp && wp.data && wp.data.dispatch) {
var d = wp.data.dispatch('core/editor');
if (d && d.editPost) {
d.editPost({ excerpt: summary });
return true;
}
}
} catch (e) { /* ignore */ }
// Fallback name selector
var named = document.querySelector('textarea[name="excerpt"]');
if (named) {
named.value = summary;
named.dispatchEvent(new Event('input', { bubbles: true }));
return true;
}
return false;
}
function setLoading(btn, on, original) {
if (on) {
btn.classList.add('is-loading');
btn.textContent = I18N.generating || '生成中…';
} else {
btn.classList.remove('is-loading');
btn.textContent = original;
}
}
document.addEventListener('click', function (e) {
// SEO generate
var seoBtn = e.target.closest('.jio-ai-seo-generate');
if (seoBtn) {
e.preventDefault();
if (seoBtn.classList.contains('is-loading') || seoBtn.disabled) return;
var type = seoBtn.getAttribute('data-type') || 'post';
var id = parseInt(seoBtn.getAttribute('data-id') || '0', 10);
if (!id) {
notify(seoBtn, I18N.needSave || '请先保存后再生成 SEO', 'error');
return;
}
if (CFG.ai && CFG.ai.available === false) {
notify(seoBtn, I18N.notAvailable || 'AI 未配置或未启用', 'error');
return;
}
var seoOriginal = seoBtn.textContent;
setLoading(seoBtn, true, seoOriginal);
fetch(REST + '/ai/seo-generate', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': NONCE,
},
body: JSON.stringify({ type: type, id: id }),
})
.then(function (r) {
return r.json().then(function (json) {
return { ok: r.ok, json: json };
});
})
.then(function (res) {
setLoading(seoBtn, false, seoOriginal);
if (!res.ok || !res.json || res.json.success === false) {
var msg =
(res.json && (res.json.message || (res.json.data && res.json.data.message))) ||
I18N.failed ||
'生成失败';
notify(seoBtn, msg, 'error');
return;
}
var data = res.json.data || {};
if (type === 'term') fillTerm(data);
else fillPost(data);
var errors = collectErrors(data);
if (errors.length) notify(seoBtn, errors.join(';'), 'error');
else notify(seoBtn, I18N.success || '已填入 SEO 字段,请检查后更新保存', 'ok');
})
.catch(function () {
setLoading(seoBtn, false, seoOriginal);
notify(seoBtn, I18N.failed || '生成失败', 'error');
});
return;
}
// Summary → excerpt
var sumBtn = e.target.closest('.jio-ai-summary-generate');
if (!sumBtn) return;
e.preventDefault();
if (sumBtn.classList.contains('is-loading') || sumBtn.disabled) return;
if (CFG.ai && CFG.ai.available === false) {
notify(sumBtn, I18N.notAvailable || 'AI 未配置或未启用', 'error');
return;
}
var content = getEditorContent();
if (!content) {
notify(sumBtn, I18N.needContent || '请先填写正文内容', 'error');
return;
}
var sumOriginal = sumBtn.textContent;
setLoading(sumBtn, true, sumOriginal);
fetch(REST + '/ai/summary-suggest', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': NONCE,
},
body: JSON.stringify({
title: getEditorTitle(),
content: content,
max_length: 200,
}),
})
.then(function (r) {
return r.json().then(function (json) {
return { ok: r.ok, json: json };
});
})
.then(function (res) {
setLoading(sumBtn, false, sumOriginal);
if (!res.ok || !res.json || res.json.success === false) {
var j = res.json || {};
var msg =
j.message ||
(j.data && (j.data.message || (Array.isArray(j.data) && j.data[0] && j.data[0].message))) ||
I18N.failed ||
'生成失败';
if (res.status === 401 || res.status === 403) {
msg = I18N.needLogin || '登录已失效或权限不足,请刷新页面后重试';
}
notify(sumBtn, msg, 'error');
return;
}
var payload = res.json.data || {};
var summary = payload.summary || (payload.data && payload.data.summary) || '';
if (!summary) {
notify(sumBtn, I18N.failed || '生成失败:返回摘要为空', 'error');
return;
}
if (!setExcerptValue(summary)) {
notify(sumBtn, I18N.noExcerptField || '未找到摘要字段,请展开「摘要」面板', 'error');
return;
}
notify(sumBtn, I18N.summarySuccess || '已填入摘要字段,请检查后更新保存', 'ok');
})
.catch(function (err) {
setLoading(sumBtn, false, sumOriginal);
notify(sumBtn, (err && err.message) || I18N.failed || '生成失败', 'error');
});
});
})();