diff --git a/conf.example.yml b/conf.example.yml index c826c41..ff22e59 100644 --- a/conf.example.yml +++ b/conf.example.yml @@ -25,29 +25,8 @@ pageInfo: description: Self-hosted unified navigation navLinks: [] footer: '' -sections: -- name: Favorites - icon: fas fa-star - collapsed: false - items: - - title: Gitea - description: Code repository - icon: fas fa-code-branch - url: http://192.168.1.100:3000 - statusCheck: true -- name: History - icon: fas fa-history - collapsed: false - items: - - title: 安装浏览历史插件 - description: 一键安装,自动按访问频次排序 - icon: fas fa-download - url: /extension/install.html -- name: Tools - icon: fas fa-tools - collapsed: true - items: - - title: Example Service - description: Add your own services - icon: fas fa-cog - url: http://192.168.1.100:8080 +sections: [] +# 分组由管理后台(/admin/ 的「导航分组」)增删,初始为空: +# 新建一个分组后再往里添加服务即可。 +# 「历史访问」分组由 history-server 根据浏览记录自动创建, +# 请不要手工写在这里。 diff --git a/history-server/main.go b/history-server/main.go index 1b0e1c9..eb1e47b 100644 --- a/history-server/main.go +++ b/history-server/main.go @@ -537,11 +537,25 @@ func rebuildDashySections() { break } } + foundHist := false for _, si := range secs { if isHistSection(si.name) { si.raw["items"] = histItems + foundHist = true } } + // 配置里没有「历史访问」分组时补一个。该分组只能由这里创建: + // 管理后台明确禁止手工新建/改名/删除它(否则下次同步就被覆写), + // 所以若只靠用户手动建,功能就一直不会出现。 + if !foundHist { + sectionsRaw = append(sectionsRaw, map[string]any{ + "name": histSectionZh, + "icon": "fas fa-history", + "collapsed": false, + "items": histItems, + }) + fmt.Println("[history] 已补建「历史访问」分组") + } } cfg["sections"] = sectionsRaw diff --git a/web/navi.css b/web/navi.css index 4c81cc0..ff8ba6d 100644 --- a/web/navi.css +++ b/web/navi.css @@ -1034,6 +1034,84 @@ html[data-theme] .navi-ws-notice-url { word-break: break-all !important; } +/* ── 「不支持在工作区显示」弹层 ── + 被拒内嵌时不能只是面板里一行字:用户点了必须有明确反馈。 + z-index 低于 Dashy 自己的配置弹层(10000),避免挡住它。 */ +html[data-theme] .navi-ws-modal { + position: fixed !important; + inset: 0 !important; + z-index: 9990 !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + padding: 18px !important; + background: rgba(15, 23, 42, .28) !important; + backdrop-filter: blur(4px) !important; + -webkit-backdrop-filter: blur(4px) !important; + animation: navi-fade-in .18s ease both; +} + +html[data-theme] .navi-ws-modal-card { + box-sizing: border-box !important; + width: min(420px, 100%) !important; + display: flex !important; + flex-direction: column !important; + align-items: center !important; + gap: 8px !important; + padding: 24px 22px 20px !important; + text-align: center !important; + background: rgba(255, 255, 255, .96) !important; + border: 1px solid rgba(255, 255, 255, .9) !important; + border-radius: 20px !important; + box-shadow: 0 26px 60px rgba(15, 23, 42, .28), inset 0 1px 0 rgba(255, 255, 255, .95) !important; + animation: navi-fade-up .22s cubic-bezier(.22, 1, .36, 1) both; +} + +html[data-theme] .navi-ws-modal-title { + color: #0f172a !important; + font-size: 17px !important; + font-weight: 650 !important; +} + +html[data-theme] .navi-ws-modal-host { + color: #0284c7 !important; + font-size: 13px !important; + word-break: break-all !important; +} + +html[data-theme] .navi-ws-modal-text { + color: #64748b !important; + font-size: 13.5px !important; + line-height: 1.5 !important; +} + +html[data-theme] .navi-ws-modal-actions { + display: flex !important; + flex-wrap: wrap !important; + gap: 10px !important; + justify-content: center !important; + margin-top: 10px !important; +} + +html[data-theme] .navi-ws-modal-ok { + padding: 9px 18px !important; + color: #334155 !important; + font-family: inherit !important; + font-size: 13.5px !important; + background: rgba(241, 245, 249, .95) !important; + border: 1px solid rgba(203, 213, 225, .9) !important; + border-radius: 999px !important; + cursor: pointer; + -webkit-tap-highlight-color: transparent !important; + outline: none !important; +} + +html[data-theme] .navi-ws-modal-ok:hover, +html[data-theme] .navi-ws-modal-ok:focus { + color: #0f172a !important; + background: rgba(255, 255, 255, .98) !important; +} + html[data-theme] .navi-ws-open { margin-top: 6px !important; padding: 9px 18px !important; diff --git a/web/navi.js b/web/navi.js index 85ff489..ae81258 100644 --- a/web/navi.js +++ b/web/navi.js @@ -290,7 +290,9 @@ notice.append(wsText('navi-ws-notice-text', reason ? '服务器返回:' + reason : '服务器返回了禁止内嵌的响应头。')); - notice.append(wsText('navi-ws-notice-url', openUrl || url || '')); + // 注意:这里只能用 url。早先本函数第 4 个参数叫 openUrl,改成 note 之后 + // 本分支漏改,导致每次被拒内嵌都抛 ReferenceError,后面的弹层根本执行不到。 + notice.append(wsText('navi-ws-notice-url', url || '')); const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'navi-ws-open'; @@ -298,7 +300,7 @@ btn.addEventListener('click', (evt) => { evt.preventDefault(); evt.stopPropagation(); - if (nativeWindowOpen) nativeWindowOpen(openUrl || url, '_blank'); + if (nativeWindowOpen) nativeWindowOpen(url, '_blank'); }); notice.append(btn); } @@ -357,6 +359,60 @@ return bar; } + // 被拒内嵌时明确弹一个提示(不只是面板里的一行字): + // 用户点了却什么也没发生是最差的体验。 + let wsModal = null; + + function closeWorkspaceModal() { + if (wsModal && wsModal.overlay.parentNode) wsModal.overlay.parentNode.removeChild(wsModal.overlay); + wsModal = null; + } + + function showWorkspaceBlockedModal(url, reason) { + closeWorkspaceModal(); + const overlay = document.createElement('div'); + overlay.className = 'navi-ws-modal'; + const card = document.createElement('div'); + card.className = 'navi-ws-modal-card'; + const title = document.createElement('div'); + title.className = 'navi-ws-modal-title'; + title.textContent = '该应用不支持在工作区显示'; + const host = document.createElement('div'); + host.className = 'navi-ws-modal-host'; + host.textContent = hostOf(url); + const text = document.createElement('div'); + text.className = 'navi-ws-modal-text'; + text.textContent = reason + ? ('服务器返回:' + reason + ',所以它不允许被嵌在其它页面里。') + : '服务器返回了禁止内嵌的响应头,所以它不允许被嵌在其它页面里。'; + const actions = document.createElement('div'); + actions.className = 'navi-ws-modal-actions'; + const openBtn = document.createElement('button'); + openBtn.type = 'button'; + openBtn.className = 'navi-ws-open'; + openBtn.textContent = '在新标签页打开'; + openBtn.addEventListener('click', () => { + closeWorkspaceModal(); + if (nativeWindowOpen) nativeWindowOpen(url, '_blank'); + }); + const okBtn = document.createElement('button'); + okBtn.type = 'button'; + okBtn.className = 'navi-ws-modal-ok'; + okBtn.textContent = '知道了'; + okBtn.addEventListener('click', closeWorkspaceModal); + actions.append(openBtn, okBtn); + card.append(title, host, text, actions); + overlay.append(card); + overlay.addEventListener('click', (evt) => { + if (evt.target === overlay) closeWorkspaceModal(); + }); + document.addEventListener('keydown', (evt) => { + if (evt.key === 'Escape') closeWorkspaceModal(); + }); + document.body.append(overlay); + wsModal = { overlay }; + } + async function frameable(url) { if (wsFrameCache.has(url)) return wsFrameCache.get(url); let info = { frameable: true, conclusive: false, reason: '' }; @@ -393,6 +449,7 @@ wsLastUrl = ''; parts.frame.removeAttribute('src'); setWorkspaceState('blocked', url, info.reason, url); + showWorkspaceBlockedModal(url, info.reason); return; } wsLastUrl = url;