导航页现在会自动检测浏览历史插件是否已安装: - 扩展在用户配置的服务地址上动态注册 content script (Chrome: chrome.scripting.registerContentScripts, Firefox: browser.contentScripts.register), 于 document_start 给 <html> 打上 data-navi-ext 标记, 不在其他站点运行。 - history-server 始终把「安装浏览历史插件」放在「历史访问」第一条 (入口 url=/extension/install.html),启动时也会重建一次, 不再需要等下一次同步。 - web/navi.js 读到标记后隐藏该入口(连 .item-wrapper 一起隐藏, 避免留下空网格位)。 - 新增 /extension/install.html:自动识别浏览器,给出对应安装包与步骤; 支持在 data 卷放 extension-store.json 配置商店链接, 有商店链接时显示真正的「一键安装」。 - 段落名匹配同时兼容中英文(常用服务/Favorites、历史访问/History), 修掉了公开示例 conf.yml 用英文名时重建逻辑完全失效的问题。 - 扩展升到 1.1.0(新增 scripting 权限与 detect.js)。 Note: Chrome/Edge 不允许从网站直接安装非商店扩展, 因此自托管场景下只能下载后手动加载;发布到商店后才会出现一键安装。
63 lines
2.1 KiB
JavaScript
63 lines
2.1 KiB
JavaScript
(() => {
|
||
'use strict';
|
||
|
||
// 浏览器插件在 document_start 阶段写入这个属性(见 extension/*/detect.js)。
|
||
const EXT_ATTR = 'data-navi-ext';
|
||
const INSTALL_SELECTOR = 'a[href*="/extension/install"]';
|
||
|
||
function extensionInstalled() {
|
||
return document.documentElement.hasAttribute(EXT_ATTR);
|
||
}
|
||
|
||
// 已装插件的浏览器不需要安装入口:连外层网格单元一起隐藏,
|
||
// 否则 .item-wrapper 会留下一个空位。
|
||
function hideInstallEntry() {
|
||
if (!extensionInstalled()) return;
|
||
document.querySelectorAll(INSTALL_SELECTOR).forEach((link) => {
|
||
const cell = link.closest('.item-wrapper') || link.parentElement || link;
|
||
if (cell.style.display !== 'none') cell.style.display = 'none';
|
||
});
|
||
}
|
||
|
||
function mountSearch() {
|
||
if (document.querySelector('.navi-search-bar')) return;
|
||
const form = document.createElement('form');
|
||
form.className = 'navi-search-bar';
|
||
form.method = 'GET';
|
||
form.action = '/search';
|
||
form.target = '_blank';
|
||
form.setAttribute('role', 'search');
|
||
|
||
const icon = document.createElement('span');
|
||
icon.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>';
|
||
icon.setAttribute('aria-hidden', 'true');
|
||
|
||
const input = document.createElement('input');
|
||
input.type = 'search';
|
||
input.name = 'q';
|
||
input.placeholder = '搜索…';
|
||
input.autocomplete = 'off';
|
||
input.setAttribute('aria-label', '搜索');
|
||
|
||
const button = document.createElement('button');
|
||
button.type = 'submit';
|
||
button.textContent = '→';
|
||
button.title = '搜索';
|
||
|
||
form.append(icon, input, button);
|
||
document.body.appendChild(form);
|
||
}
|
||
|
||
function run() {
|
||
mountSearch();
|
||
hideInstallEntry();
|
||
}
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', run, { once: true });
|
||
} else {
|
||
run();
|
||
}
|
||
new MutationObserver(run).observe(document.documentElement, { childList: true, subtree: true });
|
||
})();
|