feat: 实现MeNav浏览器扩展支持接口
为支持浏览器扩展的HTML替换方案,对原仓库进行以下修改: - 在generator.js中添加配置数据序列化和嵌入功能 - 在default.hbs中添加配置数据存储元素 - 在site-card.hbs和category.hbs中添加数据属性标识符 - 在script.js中添加全局MeNav对象和API方法
This commit is contained in:
@@ -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)) {
|
if (Array.isArray(renderData.navigation)) {
|
||||||
renderData.navigation = renderData.navigation.map((item, index) => {
|
renderData.navigation = renderData.navigation.map((item, index) => {
|
||||||
@@ -824,7 +831,10 @@ function generateHTML(config) {
|
|||||||
currentYear,
|
currentYear,
|
||||||
socialLinks,
|
socialLinks,
|
||||||
navigation: generateNavigation(config.navigation, config), // 兼容旧版
|
navigation: generateNavigation(config.navigation, config), // 兼容旧版
|
||||||
social: Array.isArray(config.social) ? config.social : [] // 兼容旧版
|
social: Array.isArray(config.social) ? config.social : [], // 兼容旧版
|
||||||
|
|
||||||
|
// 确保配置数据可用于浏览器扩展
|
||||||
|
configJSON: config.configJSON // 从prepareRenderData函数中获取的配置数据
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
193
src/script.js
193
src/script.js
@@ -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', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// 先声明所有状态变量
|
// 先声明所有状态变量
|
||||||
let isSearchActive = false;
|
let isSearchActive = false;
|
||||||
@@ -764,6 +946,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// 初始化搜索引擎选择
|
// 初始化搜索引擎选择
|
||||||
initSearchEngine();
|
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延迟
|
// 立即执行初始化,不再使用requestAnimationFrame延迟
|
||||||
// 显示首页
|
// 显示首页
|
||||||
showPage('home');
|
showPage('home');
|
||||||
|
|||||||
@@ -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>
|
<h2><i class="{{icon}}"></i> {{name}}</h2>
|
||||||
<div class="sites-grid">
|
<div class="sites-grid" data-menav-category="{{name}}">
|
||||||
{{#if sites.length}}
|
{{#if sites.length}}
|
||||||
{{#each sites}}
|
{{#each sites}}
|
||||||
{{> site-card}}
|
{{> site-card}}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
{{#if url}}
|
{{#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>
|
<i class="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}}"></i>
|
||||||
<h3>{{#if name}}{{name}}{{else}}未命名站点{{/if}}</h3>
|
<h3>{{#if name}}{{name}}{{else}}未命名站点{{/if}}</h3>
|
||||||
<p>{{#if description}}{{description}}{{else}}{{url}}{{/if}}</p>
|
<p>{{#if description}}{{description}}{{else}}{{url}}{{/if}}</p>
|
||||||
|
|||||||
@@ -123,6 +123,10 @@
|
|||||||
<i class="fas fa-moon"></i>
|
<i class="fas fa-moon"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 配置数据 - 用于浏览器扩展 -->
|
||||||
|
<script id="menav-config-data" type="application/json" style="display: none;">
|
||||||
|
{{{configJSON}}}
|
||||||
|
</script>
|
||||||
<script src="script.js"></script>
|
<script src="script.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user