feat: 实现MeNav浏览器扩展支持接口

为支持浏览器扩展的HTML替换方案,对原仓库进行以下修改:
- 在generator.js中添加配置数据序列化和嵌入功能
- 在default.hbs中添加配置数据存储元素
- 在site-card.hbs和category.hbs中添加数据属性标识符
- 在script.js中添加全局MeNav对象和API方法
This commit is contained in:
Zuoling Rong
2025-05-22 22:58:02 +08:00
parent b7edd1dcbf
commit c72f7fd9f5
5 changed files with 432 additions and 220 deletions

View File

@@ -402,6 +402,13 @@ function prepareRenderData(config) {
// 移除警告日志,数据处理逻辑保留
}
// 添加序列化的配置数据,用于浏览器扩展
renderData.configJSON = JSON.stringify({
version: process.env.npm_package_version || '1.0.0',
timestamp: new Date().toISOString(),
data: config
});
// 添加导航项的活动状态标记和子菜单
if (Array.isArray(renderData.navigation)) {
renderData.navigation = renderData.navigation.map((item, index) => {
@@ -824,7 +831,10 @@ function generateHTML(config) {
currentYear,
socialLinks,
navigation: generateNavigation(config.navigation, config), // 兼容旧版
social: Array.isArray(config.social) ? config.social : [] // 兼容旧版
social: Array.isArray(config.social) ? config.social : [], // 兼容旧版
// 确保配置数据可用于浏览器扩展
configJSON: config.configJSON // 从prepareRenderData函数中获取的配置数据
};
try {

View File

@@ -1,3 +1,185 @@
// 全局MeNav对象 - 用于浏览器扩展
window.MeNav = {
version: "1.0.0",
// 获取配置数据
getConfig: function() {
const configData = document.getElementById('menav-config-data');
return configData ? JSON.parse(configData.textContent) : null;
},
// 更新DOM元素
updateElement: function(id, newData) {
const element = document.querySelector(`[data-menav-id="${id}"]`);
if (!element) return false;
// 根据元素类型更新内容
const type = element.getAttribute('data-menav-type');
if (type === 'site') {
// 更新站点卡片
if (newData.url) element.href = newData.url;
if (newData.name) element.querySelector('h3').textContent = newData.name;
if (newData.description) element.querySelector('p').textContent = newData.description;
if (newData.icon) {
const iconElement = element.querySelector('i');
if (iconElement) {
iconElement.className = newData.icon;
}
}
if (newData.title) element.title = newData.title;
// 触发元素更新事件
this.events.emit('elementUpdated', {
id: id,
type: 'site',
data: newData
});
return true;
} else if (type === 'category') {
// 更新分类
if (newData.name) {
const titleElement = element.querySelector('h2');
if (titleElement) {
// 保留图标
const iconElement = titleElement.querySelector('i');
const iconClass = iconElement ? iconElement.className : '';
titleElement.innerHTML = `<i class="${newData.icon || iconClass}"></i> ${newData.name}`;
}
}
// 触发元素更新事件
this.events.emit('elementUpdated', {
id: id,
type: 'category',
data: newData
});
return true;
}
return false;
},
// 添加新元素
addElement: function(type, parentId, data) {
const parent = document.querySelector(`[data-menav-id="${parentId}"]`);
if (!parent) return null;
if (type === 'site' && parent.getAttribute('data-menav-type') === 'category') {
// 添加站点卡片到分类
const sitesGrid = parent.querySelector('.sites-grid');
if (!sitesGrid) return null;
// 创建新的站点卡片
const newSite = document.createElement('a');
newSite.className = 'site-card';
newSite.href = data.url || '#';
newSite.title = data.name + (data.description ? ' - ' + data.description : '');
const elementId = `site-new-${Date.now()}`;
newSite.setAttribute('data-menav-id', elementId);
newSite.setAttribute('data-menav-type', 'site');
newSite.setAttribute('data-menav-category', parent.id);
// 添加内容
newSite.innerHTML = `
<i class="${data.icon || 'fas fa-link'}"></i>
<h3>${data.name || '未命名站点'}</h3>
<p>${data.description || ''}</p>
`;
// 添加到DOM
sitesGrid.appendChild(newSite);
// 移除"暂无网站"提示(如果存在)
const emptyMessage = sitesGrid.querySelector('.empty-sites');
if (emptyMessage) {
emptyMessage.remove();
}
// 触发元素添加事件
this.events.emit('elementAdded', {
id: elementId,
type: 'site',
parentId: parentId,
data: data
});
return elementId;
}
return null;
},
// 删除元素
removeElement: function(id) {
const element = document.querySelector(`[data-menav-id="${id}"]`);
if (!element) return false;
// 获取元素类型和分类(如果是站点卡片)
const type = element.getAttribute('data-menav-type');
const category = element.getAttribute('data-menav-category');
// 删除元素
element.remove();
// 触发元素删除事件
this.events.emit('elementRemoved', {
id: id,
type: type,
category: category
});
return true;
},
// 获取所有元素
getAllElements: function(type) {
return Array.from(document.querySelectorAll(`[data-menav-type="${type}"]`)).map(el => {
return {
id: el.getAttribute('data-menav-id'),
type: el.getAttribute('data-menav-type'),
element: el
};
});
},
// 事件系统
events: {
listeners: {},
// 添加事件监听器
on: function(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
return this;
},
// 触发事件
emit: function(event, data) {
if (this.listeners[event]) {
this.listeners[event].forEach(callback => callback(data));
}
return this;
},
// 移除事件监听器
off: function(event, callback) {
if (this.listeners[event]) {
if (callback) {
this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
} else {
delete this.listeners[event];
}
}
return this;
}
}
};
document.addEventListener('DOMContentLoaded', () => {
// 先声明所有状态变量
let isSearchActive = false;
@@ -764,6 +946,17 @@ document.addEventListener('DOMContentLoaded', () => {
// 初始化搜索引擎选择
initSearchEngine();
// 初始化MeNav对象版本信息
try {
const config = window.MeNav.getConfig();
if (config && config.version) {
window.MeNav.version = config.version;
console.log('MeNav API initialized with version:', config.version);
}
} catch (error) {
console.error('Error initializing MeNav API:', error);
}
// 立即执行初始化不再使用requestAnimationFrame延迟
// 显示首页
showPage('home');

View File

@@ -1,6 +1,6 @@
<section class="category" id="{{name}}">
<section class="category" id="{{name}}" data-menav-id="category-{{@index}}" data-menav-type="category">
<h2><i class="{{icon}}"></i> {{name}}</h2>
<div class="sites-grid">
<div class="sites-grid" data-menav-category="{{name}}">
{{#if sites.length}}
{{#each sites}}
{{> site-card}}

View File

@@ -1,5 +1,10 @@
{{#if url}}
<a href="{{url}}" class="site-card{{#if style}} site-card-{{style}}{{/if}}" title="{{name}} - {{#if description}}{{description}}{{else}}{{url}}{{/if}}" {{#if external}}target="_blank" rel="noopener"{{/if}}>
<a href="{{url}}" class="site-card{{#if style}} site-card-{{style}}{{/if}}"
title="{{name}} - {{#if description}}{{description}}{{else}}{{url}}{{/if}}"
{{#if external}}target="_blank" rel="noopener"{{/if}}
data-menav-id="site-{{@index}}"
data-menav-type="site"
data-menav-category="{{../name}}">
<i class="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}}"></i>
<h3>{{#if name}}{{name}}{{else}}未命名站点{{/if}}</h3>
<p>{{#if description}}{{description}}{{else}}{{url}}{{/if}}</p>

View File

@@ -123,6 +123,10 @@
<i class="fas fa-moon"></i>
</button>
</div>
<!-- 配置数据 - 用于浏览器扩展 -->
<script id="menav-config-data" type="application/json" style="display: none;">
{{{configJSON}}}
</script>
<script src="script.js"></script>
</body>
</html>