初始提交 - FutureOSS v1.0 插件化运行时框架

一切皆为插件的开发者工具运行时框架

🧩 核心特性:
  - 插件热插拔 (importlib 动态加载)
  - 依赖自动解析 (拓扑排序 + 循环检测)
  - 企业级稳定 (熔断/降级/重试/隔离)
  - 事件驱动 (发布/订阅事件总线)
  - 完整配置 (YAML 配置 + 热重载)
This commit is contained in:
Falck
2026-04-06 09:57:10 +08:00
commit 76147bae94
174 changed files with 15626 additions and 0 deletions

View File

@@ -0,0 +1,252 @@
/**
* OSS Community Editor JS
* Markdown 编辑器、实时预览、表单提交
*/
document.addEventListener('DOMContentLoaded', () => {
initEditor();
initToolbar();
initTags();
initForm();
});
// 初始化编辑器
function initEditor() {
const textarea = document.getElementById('postContent');
const titleInput = document.getElementById('postTitle');
// Tab 键支持
if (textarea) {
textarea.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
e.preventDefault();
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
textarea.value = textarea.value.substring(0, start) + ' ' + textarea.value.substring(end);
textarea.selectionStart = textarea.selectionEnd = start + 2;
}
});
}
}
// 初始化工具栏
function initToolbar() {
const buttons = document.querySelectorAll('.md-btn');
const textarea = document.getElementById('postContent');
if (!textarea) return;
buttons.forEach(btn => {
btn.addEventListener('click', () => {
const action = btn.dataset.md;
insertMarkdown(textarea, action);
});
});
}
function insertMarkdown(textarea, action) {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selected = textarea.value.substring(start, end);
let insertion = '';
switch (action) {
case 'bold':
insertion = `**${selected || '粗体文本'}**`;
break;
case 'italic':
insertion = `*${selected || '斜体文本'}*`;
break;
case 'heading':
insertion = `\n## ${selected || '标题'}\n`;
break;
case 'quote':
insertion = `\n> ${selected || '引用文本'}\n`;
break;
case 'code':
insertion = selected.includes('\n') ? `\n\`\`\`\n${selected || '代码块'}\n\`\`\`\n` : `\`${selected || '行内代码'}\``;
break;
case 'link':
insertion = `[${selected || '链接文本'}](url)`;
break;
case 'list':
insertion = `\n- ${selected || '列表项'}\n`;
break;
}
textarea.value = textarea.value.substring(0, start) + insertion + textarea.value.substring(end);
textarea.focus();
textarea.selectionStart = textarea.selectionEnd = start + insertion.length;
}
// 初始化标签
function initTags() {
const tagInput = document.getElementById('tagInput');
const tagsContainer = document.getElementById('tagsContainer');
if (!tagInput || !tagsContainer) return;
tagInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
const tagName = tagInput.value.trim();
if (tagName) {
addTag(tagName);
tagInput.value = '';
}
}
});
}
function addTag(name) {
const tagsContainer = document.getElementById('tagsContainer');
if (!tagsContainer) return;
// 检查是否已存在
const existing = tagsContainer.querySelectorAll('.tag-item');
for (const tag of existing) {
if (tag.textContent.trim().replace('×', '').trim() === name) {
return;
}
}
const tagEl = document.createElement('span');
tagEl.className = 'tag-item';
tagEl.innerHTML = `
${name}
<button type="button" class="tag-remove" onclick="this.parentElement.remove()">&times;</button>
`;
tagsContainer.appendChild(tagEl);
}
// 初始化表单
function initForm() {
const form = document.getElementById('postEditorForm');
const saveBtn = document.getElementById('savePostBtn');
if (!form || !saveBtn) return;
saveBtn.addEventListener('click', async (e) => {
e.preventDefault();
const postId = document.getElementById('editPostId').value;
const title = document.getElementById('postTitle').value.trim();
const content = document.getElementById('postContent').value.trim();
const categoryId = document.getElementById('postCategory').value;
// 收集标签
const tags = [];
document.querySelectorAll('#tagsContainer .tag-item').forEach(tag => {
const name = tag.textContent.replace('×', '').trim();
if (name) tags.push(name);
});
// 验证
if (!title) {
showError('请输入帖子标题');
return;
}
if (title.length < 5) {
showError('标题至少 5 个字符');
return;
}
if (!content) {
showError('请输入帖子内容');
return;
}
if (content.length < 10) {
showError('内容至少 10 个字符');
return;
}
if (!categoryId) {
showError('请选择分类');
return;
}
// 提交
setSaveButtonLoading(true);
const formData = {
title: title,
content: content,
category_id: categoryId,
tags: tags
};
if (postId) {
formData.id = parseInt(postId);
}
const action = postId ? 'update' : 'create';
const url = `api/posts.php?action=${action}`;
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
const result = await response.json();
if (result.success) {
showSuccess(postId ? '更新成功!' : '发布成功!正在跳转...');
setTimeout(() => {
window.location.href = `post.php?id=${result.post_id || postId}`;
}, 1000);
} else {
showError(result.message || '操作失败');
}
} catch (error) {
showError('网络错误,请稍后重试');
} finally {
setSaveButtonLoading(false);
}
});
}
function showSuccess(message) {
const toast = document.getElementById('successToast');
const msgEl = document.getElementById('successMessage');
if (toast && msgEl) {
msgEl.textContent = message;
toast.style.display = 'flex';
setTimeout(() => { toast.style.display = 'none'; }, 3000);
}
}
function showError(message) {
const toast = document.getElementById('errorToast');
const msgEl = document.getElementById('errorMessage');
if (toast && msgEl) {
msgEl.textContent = message;
toast.style.display = 'flex';
setTimeout(() => { toast.style.display = 'none'; }, 4000);
}
}
function setSaveButtonLoading(loading) {
const btn = document.getElementById('savePostBtn');
if (!btn) return;
if (loading) {
btn.disabled = true;
btn.style.opacity = '0.6';
btn.innerHTML = `
<svg class="btn-spinner" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" stroke-linecap="round" opacity="0.25"/>
<path d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" fill="currentColor" opacity="0.75"/>
</svg>
处理中...
`;
} else {
btn.disabled = false;
btn.style.opacity = '1';
btn.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
</svg>
${document.getElementById('editPostId').value ? '保存修改' : '发布帖子'}
`;
}
}