feat(web): 隐藏原生设置、圆形视图切换、工作空间改列表、三视图标题统一
用户提出的四条,都基于实测数据而不是猜测: 1) 隐藏 Dashy 原生设置入口 顶栏的 .config-launcher(齿轮+铅笔+调色板)与我们的 /admin/ 重复, 连同 .options-trigger/.settings-outer 一起隐藏。 2) 视图切换图标改成圆形,紧贴自定义入口左侧 原生切换按钮是侧栏底部的方形图标(.switch-view-buttons),已隐藏; 改为右下角圆形悬浮按钮 .navi-view-switch,点开是「首页/极简/工作空间」 菜单并标出当前视图。 顺带修掉 2px 错位:管理入口是内容盒(48+2px 边框=50),视图按钮是 边框盒(48),并排时差 2px;给管理入口补 box-sizing: border-box。 3) 工作空间「点了没反应」 读 Dashy 自己的 Workspace 组件(assets/Workspace-*.js)得到结论: 右侧面板就是一个 iframe,launchApp() 只有 target 不是 newtab 才把 URL 灌进面板;我们全局 openingMethod: newtab,所以每次点击都走 window.open 分支,this.url 恒为空 → 面板永远空白。 就算灌进去也没用:Gitea 实测 X-Frame-Options: sameorigin 拒绝被嵌 (Refused to display … in a frame)。 所以改成:点左侧分组 → 右侧列出该分组应用卡片(数据取自 /admin/api/sections),不依赖目标站点是否允许 iframe,首页仍保持新标签页。 4) 三视图标题割裂 实测 /minimal 是 48px 居中 -1.92px,而 / 与 /workspace 是顶栏里 40px 左对齐、无字距。现在三个视图共用同一条规则(clamp(1.75rem,4vw,2.75rem)、 700、-0.035em、居中;移动端 clamp(1.6rem,8vw,2.2rem)),副标题也居中置灰。 另外:工作空间面板原本用 position: fixed 钉在视口中间(1364×654,上距顶栏 7px、下距搜索栏 8px),把页面切成「顶栏/大方块/底部」三段;改为正常文档流, 内容多时页面自然滚动。
This commit is contained in:
447
web/navi.css
447
web/navi.css
@ -6,21 +6,44 @@ html {
|
||||
text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
/* 禁用移动端/Chromium 对可点击元素注入的蓝色点击闪烁。 */
|
||||
html[data-theme] *,
|
||||
html[data-theme] *::before,
|
||||
html[data-theme] *::after {
|
||||
-webkit-tap-highlight-color: transparent !important;
|
||||
}
|
||||
|
||||
html[data-theme] body {
|
||||
/* border-box 让 padding 计入 min-height:否则 min-height:100vh 与
|
||||
padding-bottom 相加,短页面(极简/工作空间)必然多出一屏滚动。 */
|
||||
box-sizing: border-box !important;
|
||||
min-width: 0;
|
||||
overflow-x: hidden;
|
||||
/* 悬浮搜索栏固定在底部,给内容留出空间,避免遮挡页脚备案号。 */
|
||||
padding-bottom: 84px !important;
|
||||
/* 列向 flex + 页脚 margin-top:auto,把备案号顶到页面底部而非浮在中间。 */
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
min-height: 100vh !important;
|
||||
/* 备案号占据真正的页面最底部;悬浮控件在它上方,不再用底部 padding
|
||||
把页脚整体抬高。 */
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* Dashy 的 #dashy 本身撑满 100vh;不扣掉留给悬浮搜索栏的高度,
|
||||
短页面(极简视图/工作空间)就会凭空多出约 134px 的纵向滚动。 */
|
||||
html[data-theme] #app {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* Dashy 的 #dashy 默认 min-height:100vh,短页面叠加页脚后必出滚动;
|
||||
交回内容自然撑高,视口高度由 body 兜住。 */
|
||||
html[data-theme] #dashy {
|
||||
min-height: calc(100vh - 84px) !important;
|
||||
min-height: auto !important;
|
||||
}
|
||||
|
||||
/* Dashy's native filter/settings strip duplicates the portal search and breaks on phones. */
|
||||
/* Dashy's native filter/settings strip duplicates the portal search and breaks on phones.
|
||||
原生设置入口(齿轮/铅笔/调色板)与我们的 /admin/ 重复,统一隐藏。 */
|
||||
html[data-theme] .home > .settings-outer,
|
||||
html[data-theme] .config-launcher,
|
||||
html[data-theme] .options-trigger,
|
||||
html[data-theme] .settings-outer,
|
||||
html[data-theme] .minimal-home .title-and-search > form {
|
||||
display: none !important;
|
||||
}
|
||||
@ -98,7 +121,7 @@ html[data-theme] .navi-search-bar {
|
||||
box-sizing: border-box !important;
|
||||
position: fixed !important;
|
||||
left: calc(16px + env(safe-area-inset-left, 0px)) !important;
|
||||
bottom: calc(16px + env(safe-area-inset-bottom, 0px)) !important;
|
||||
bottom: calc(76px + env(safe-area-inset-bottom, 0px)) !important;
|
||||
margin: 0 !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
@ -144,20 +167,37 @@ html[data-theme] .navi-search-toggle {
|
||||
border: 0 !important;
|
||||
border-radius: 50% !important;
|
||||
cursor: pointer;
|
||||
transition: background .2s ease, color .2s ease, transform .2s cubic-bezier(.22, 1, .36, 1) !important;
|
||||
appearance: none !important;
|
||||
-webkit-appearance: none !important;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
transition: transform .28s cubic-bezier(.22, 1, .36, 1) !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-search-toggle:hover {
|
||||
color: #0284c7 !important;
|
||||
background: rgba(14, 165, 233, .12) !important;
|
||||
transform: scale(1.06);
|
||||
/* 点击与聚焦都不变蓝,只保留轻微缩放反馈。 */
|
||||
html[data-theme] .navi-search-toggle:hover,
|
||||
html[data-theme] .navi-search-toggle:focus,
|
||||
html[data-theme] .navi-search-toggle:active {
|
||||
color: #0f172a !important;
|
||||
background: transparent !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
html[data-theme] .navi-search-toggle:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme] .navi-search-toggle:active {
|
||||
transform: scale(.96);
|
||||
}
|
||||
|
||||
/* 右侧管理入口:Dashy 页面本身没有去 /admin/ 的链接 */
|
||||
html[data-theme] .navi-admin-entry {
|
||||
box-sizing: border-box !important;
|
||||
position: fixed !important;
|
||||
right: calc(16px + env(safe-area-inset-right, 0px)) !important;
|
||||
bottom: calc(16px + env(safe-area-inset-bottom, 0px)) !important;
|
||||
bottom: calc(76px + env(safe-area-inset-bottom, 0px)) !important;
|
||||
width: 52px !important;
|
||||
height: 52px !important;
|
||||
display: grid !important;
|
||||
@ -170,13 +210,26 @@ html[data-theme] .navi-admin-entry {
|
||||
box-shadow: 0 14px 38px rgba(15, 23, 42, .16), inset 0 1px 0 rgba(255, 255, 255, .95) !important;
|
||||
backdrop-filter: blur(22px) saturate(180%) !important;
|
||||
-webkit-backdrop-filter: blur(22px) saturate(180%) !important;
|
||||
transition: transform .22s cubic-bezier(.22, 1, .36, 1), color .2s ease, background .2s ease !important;
|
||||
-webkit-tap-highlight-color: transparent !important;
|
||||
transition: transform .22s cubic-bezier(.22, 1, .36, 1), background .2s ease !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-admin-entry:hover {
|
||||
color: #0284c7 !important;
|
||||
html[data-theme] .navi-admin-entry:hover,
|
||||
html[data-theme] .navi-admin-entry:focus,
|
||||
html[data-theme] .navi-admin-entry:active {
|
||||
color: #334155 !important;
|
||||
background: rgba(255, 255, 255, .94) !important;
|
||||
transform: translateY(-1px) scale(1.05);
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
html[data-theme] .navi-admin-entry:hover {
|
||||
transform: translateY(-1px) scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme] .navi-admin-entry:active {
|
||||
transform: scale(.96);
|
||||
}
|
||||
|
||||
html[data-theme] .navi-admin-entry svg {
|
||||
@ -189,6 +242,114 @@ html[data-theme] body:has(.modal-overlay) > .navi-admin-entry {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
/* ── 视图切换:圆形悬浮按钮,紧贴管理入口左侧 ──
|
||||
原生 .switch-view-buttons 是侧栏底部的方形图标,这里一并隐藏。 */
|
||||
html[data-theme] nav.side-bar .switch-view-buttons {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-view-switch {
|
||||
position: fixed !important;
|
||||
right: calc(78px + env(safe-area-inset-right, 0px)) !important;
|
||||
bottom: calc(76px + env(safe-area-inset-bottom, 0px)) !important;
|
||||
z-index: 900 !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-view-toggle {
|
||||
box-sizing: border-box !important;
|
||||
width: 52px !important;
|
||||
height: 52px !important;
|
||||
display: grid !important;
|
||||
place-items: center !important;
|
||||
padding: 0 !important;
|
||||
color: #334155 !important;
|
||||
background: rgba(255, 255, 255, .72) !important;
|
||||
border: 1px solid rgba(255, 255, 255, .9) !important;
|
||||
border-radius: 50% !important;
|
||||
box-shadow: 0 14px 38px rgba(15, 23, 42, .16), inset 0 1px 0 rgba(255, 255, 255, .95) !important;
|
||||
backdrop-filter: blur(22px) saturate(180%) !important;
|
||||
-webkit-backdrop-filter: blur(22px) saturate(180%) !important;
|
||||
-webkit-tap-highlight-color: transparent !important;
|
||||
appearance: none !important;
|
||||
-webkit-appearance: none !important;
|
||||
outline: none !important;
|
||||
cursor: pointer;
|
||||
transition: transform .22s cubic-bezier(.22, 1, .36, 1), background .2s ease !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-view-toggle:hover,
|
||||
html[data-theme] .navi-view-toggle:focus,
|
||||
html[data-theme] .navi-view-toggle:active {
|
||||
color: #334155 !important;
|
||||
background: rgba(255, 255, 255, .94) !important;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
html[data-theme] .navi-view-toggle:hover {
|
||||
transform: translateY(-1px) scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme] .navi-view-toggle:active {
|
||||
transform: scale(.96);
|
||||
}
|
||||
|
||||
html[data-theme] .navi-view-toggle svg {
|
||||
width: 22px !important;
|
||||
height: 22px !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-view-menu {
|
||||
position: absolute !important;
|
||||
right: 0 !important;
|
||||
bottom: 62px !important;
|
||||
min-width: 134px !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 2px !important;
|
||||
padding: 7px !important;
|
||||
background: rgba(255, 255, 255, .92) !important;
|
||||
border: 1px solid rgba(255, 255, 255, .95) !important;
|
||||
border-radius: 16px !important;
|
||||
box-shadow: 0 18px 40px rgba(15, 23, 42, .18), inset 0 1px 0 rgba(255, 255, 255, .95) !important;
|
||||
backdrop-filter: blur(20px) saturate(180%) !important;
|
||||
-webkit-backdrop-filter: blur(20px) saturate(180%) !important;
|
||||
animation: navi-fade-up .22s cubic-bezier(.22, 1, .36, 1) both;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-view-menu[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-view-option {
|
||||
padding: 9px 12px !important;
|
||||
color: #334155 !important;
|
||||
font-size: 13.5px !important;
|
||||
text-decoration: none !important;
|
||||
border-radius: 10px !important;
|
||||
white-space: nowrap !important;
|
||||
-webkit-tap-highlight-color: transparent !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-view-option:hover,
|
||||
html[data-theme] .navi-view-option:focus,
|
||||
html[data-theme] .navi-view-option:active {
|
||||
color: #0f172a !important;
|
||||
background: rgba(226, 232, 240, .7) !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-view-option.is-active {
|
||||
color: #0f172a !important;
|
||||
background: rgba(186, 230, 253, .55) !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
html[data-theme] body:has(.modal-overlay) > .navi-view-switch {
|
||||
visibility: hidden !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-search-bar input {
|
||||
min-width: 0 !important;
|
||||
height: 42px !important;
|
||||
@ -221,6 +382,8 @@ html[data-theme] .navi-search-submit {
|
||||
border-radius: 14px !important;
|
||||
box-shadow: 0 8px 20px rgba(37, 99, 235, .28) !important;
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-search-toggle svg,
|
||||
@ -229,6 +392,39 @@ html[data-theme] .navi-search-submit svg {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/* 放大镜与搜索框同步形变:圆环、手柄退场,向左收起箭头进入。 */
|
||||
html[data-theme] .navi-search-toggle svg {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-magnifier-ring,
|
||||
html[data-theme] .navi-magnifier-handle,
|
||||
html[data-theme] .navi-collapse-arrow {
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
transition: opacity .22s ease, transform .28s cubic-bezier(.22, 1, .36, 1) !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-collapse-arrow {
|
||||
opacity: 0;
|
||||
transform: translateX(5px) scale(.62);
|
||||
}
|
||||
|
||||
html[data-theme] .navi-search-bar[data-state="open"] .navi-magnifier-ring {
|
||||
opacity: 0;
|
||||
transform: translateX(-4px) scale(.28);
|
||||
}
|
||||
|
||||
html[data-theme] .navi-search-bar[data-state="open"] .navi-magnifier-handle {
|
||||
opacity: 0;
|
||||
transform: translate(-4px, -4px) rotate(-35deg) scale(.45);
|
||||
}
|
||||
|
||||
html[data-theme] .navi-search-bar[data-state="open"] .navi-collapse-arrow {
|
||||
opacity: 1;
|
||||
transform: translateX(0) scale(1);
|
||||
}
|
||||
|
||||
/* Minimal view is the phone/tablet layout used by Dashy. */
|
||||
html[data-theme] .minimal-home {
|
||||
box-sizing: border-box !important;
|
||||
@ -244,13 +440,49 @@ html[data-theme] .minimal-home .title-and-search {
|
||||
padding: 0 8px !important;
|
||||
}
|
||||
|
||||
/* ── 顶部标题:三个视图统一(同字号/字重/字距/居中)──
|
||||
此前 /minimal 是 48px 居中,而 / 与 /workspace 是顶栏里 40px 左对齐,
|
||||
同一个站点的三个视图两套标题样式,很割裂。 */
|
||||
html[data-theme] #dashy > header {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
padding: 10px 12px !important;
|
||||
}
|
||||
|
||||
html[data-theme] #dashy > header .page-titles {
|
||||
display: block !important;
|
||||
width: auto !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 auto !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
html[data-theme] #dashy > header .page-titles .text {
|
||||
display: block !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
html[data-theme] #dashy > header .page-titles .subtitle {
|
||||
display: block !important;
|
||||
margin: 6px 0 0 !important;
|
||||
color: #64748b !important;
|
||||
font-size: 13.5px !important;
|
||||
font-weight: 400 !important;
|
||||
letter-spacing: 0 !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
html[data-theme] #dashy > header .page-titles h1,
|
||||
html[data-theme] .minimal-home .title-and-search h1 {
|
||||
max-width: 100%;
|
||||
margin: 0 !important;
|
||||
color: #0f172a !important;
|
||||
font-size: clamp(2rem, 8vw, 3rem) !important;
|
||||
font-size: clamp(1.75rem, 4vw, 2.75rem) !important;
|
||||
font-weight: 700 !important;
|
||||
line-height: 1.08 !important;
|
||||
letter-spacing: -.04em !important;
|
||||
letter-spacing: -.035em !important;
|
||||
text-align: center !important;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
@ -331,8 +563,9 @@ html[data-theme] .minimal-section-inner a.item {
|
||||
padding-top: 22px !important;
|
||||
}
|
||||
|
||||
html[data-theme] #dashy > header .page-titles h1,
|
||||
html[data-theme] .minimal-home .title-and-search h1 {
|
||||
font-size: clamp(1.9rem, 9.4vw, 2.55rem) !important;
|
||||
font-size: clamp(1.6rem, 8vw, 2.2rem) !important;
|
||||
}
|
||||
|
||||
html[data-theme] .minimal-home > .item-group-container {
|
||||
@ -410,7 +643,7 @@ html[data-theme] .minimal-section-inner a.item {
|
||||
|
||||
html[data-theme] .navi-search-bar {
|
||||
left: calc(10px + env(safe-area-inset-left, 0px)) !important;
|
||||
bottom: calc(12px + env(safe-area-inset-bottom, 0px)) !important;
|
||||
bottom: calc(76px + env(safe-area-inset-bottom, 0px)) !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-search-bar[data-state="collapsed"] {
|
||||
@ -428,19 +661,25 @@ html[data-theme] .minimal-section-inner a.item {
|
||||
width: 48px !important;
|
||||
height: 48px !important;
|
||||
right: calc(10px + env(safe-area-inset-right, 0px)) !important;
|
||||
bottom: calc(12px + env(safe-area-inset-bottom, 0px)) !important;
|
||||
bottom: calc(76px + env(safe-area-inset-bottom, 0px)) !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-view-switch {
|
||||
right: calc(68px + env(safe-area-inset-right, 0px)) !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-view-toggle {
|
||||
width: 48px !important;
|
||||
height: 48px !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-view-toggle svg {
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
}
|
||||
|
||||
html[data-theme] body {
|
||||
padding-bottom: 78px !important;
|
||||
}
|
||||
|
||||
html[data-theme] #dashy {
|
||||
min-height: calc(100vh - 78px) !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-footer-host {
|
||||
bottom: calc(78px + env(safe-area-inset-bottom, 0px)) !important;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -449,7 +688,7 @@ html[data-theme] .minimal-section-inner a.item {
|
||||
内联样式里的 margin-bottom:80px(那是给旧的流式搜索栏让位的)。 */
|
||||
html[data-theme] footer {
|
||||
margin: 0 !important;
|
||||
padding: 0 0 4px !important;
|
||||
padding: 0 0 8px !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
@ -458,22 +697,13 @@ html[data-theme] footer > div {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* 极简/工作空间视图补的备案号页脚:固定悬浮在搜索栏正上方。
|
||||
它是「底部固定家具」的一部分,不能再参与文档流,否则会把页面撑高导致多余滚动。 */
|
||||
/* 极简/工作空间视图补的备案号页脚:留在文档流里,靠 body 的 flex
|
||||
把它顶到页面底部(贴着悬浮搜索栏上方),而不是 fixed 浮在内容中间。 */
|
||||
html[data-theme] .navi-footer-host {
|
||||
position: fixed !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
bottom: calc(84px + env(safe-area-inset-bottom, 0px)) !important;
|
||||
z-index: 890 !important;
|
||||
margin-top: auto !important;
|
||||
display: flex !important;
|
||||
justify-content: center !important;
|
||||
padding: 0 8px !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-footer-host .navi-footer {
|
||||
pointer-events: auto !important;
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-footer {
|
||||
@ -501,18 +731,132 @@ html[data-theme] .navi-footer a {
|
||||
color: #64748b !important;
|
||||
text-decoration: none !important;
|
||||
border-bottom: 1px dashed rgba(100, 116, 139, .45) !important;
|
||||
transition: color .2s ease, border-color .2s ease !important;
|
||||
-webkit-tap-highlight-color: transparent !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-footer a:hover {
|
||||
color: #0284c7 !important;
|
||||
border-bottom-color: rgba(2, 132, 199, .6) !important;
|
||||
html[data-theme] .navi-footer a:hover,
|
||||
html[data-theme] .navi-footer a:focus,
|
||||
html[data-theme] .navi-footer a:active {
|
||||
color: #64748b !important;
|
||||
border-bottom-color: rgba(100, 116, 139, .45) !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-footer-sep {
|
||||
color: rgba(100, 116, 139, .45) !important;
|
||||
}
|
||||
|
||||
/* ── 工作空间:右侧改为「分组应用列表」面板 ──
|
||||
Dashy 原生面板是 iframe,目标站点多数用 X-Frame-Options 拒绝被嵌,
|
||||
会呈现一片空白;这里改为直接展示我们自己的卡片列表。
|
||||
面板走正常文档流(而不是 fixed 钉在中间),否则会把页面切成
|
||||
「顶栏 / 大方块 / 底部」三段,看起来上下都被截断。 */
|
||||
html[data-theme] .work-space .web-content,
|
||||
html[data-theme] .work-space .multi-taking-view,
|
||||
html[data-theme] .work-space .workspace-widget-view {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
html[data-theme] .work-space {
|
||||
height: auto !important;
|
||||
min-height: 0 !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-ws-pane {
|
||||
box-sizing: border-box !important;
|
||||
position: static !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 14px !important;
|
||||
min-height: 340px !important;
|
||||
margin: 18px 18px 14px 72px !important;
|
||||
padding: 20px !important;
|
||||
background: rgba(255, 255, 255, .5) !important;
|
||||
border: 1px solid rgba(255, 255, 255, .82) !important;
|
||||
border-radius: 20px !important;
|
||||
box-shadow: 0 18px 44px rgba(15, 23, 42, .12), inset 0 1px 0 rgba(255, 255, 255, .92) !important;
|
||||
backdrop-filter: blur(18px) saturate(180%) !important;
|
||||
-webkit-backdrop-filter: blur(18px) saturate(180%) !important;
|
||||
z-index: 1 !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-ws-title {
|
||||
margin: 0 !important;
|
||||
color: #0f172a !important;
|
||||
font-size: 19px !important;
|
||||
font-weight: 650 !important;
|
||||
letter-spacing: -.01em !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-ws-hint {
|
||||
color: #64748b !important;
|
||||
font-size: 13.5px !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-ws-grid {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(auto-fill, minmax(216px, 1fr)) !important;
|
||||
gap: 12px !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-ws-card {
|
||||
box-sizing: border-box !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 3px !important;
|
||||
min-height: 78px !important;
|
||||
padding: 14px 16px !important;
|
||||
color: #0f172a !important;
|
||||
text-decoration: none !important;
|
||||
background: rgba(255, 255, 255, .68) !important;
|
||||
border: 1px solid rgba(255, 255, 255, .9) !important;
|
||||
border-radius: 14px !important;
|
||||
box-shadow: 0 8px 20px rgba(15, 23, 42, .07) !important;
|
||||
-webkit-tap-highlight-color: transparent !important;
|
||||
transition: transform .22s cubic-bezier(.22, 1, .36, 1), box-shadow .22s ease !important;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
html[data-theme] .navi-ws-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 14px 30px rgba(15, 23, 42, .12) !important;
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme] .navi-ws-card:active {
|
||||
transform: scale(.99);
|
||||
}
|
||||
|
||||
html[data-theme] .navi-ws-card-icon {
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
width: 26px !important;
|
||||
height: 26px !important;
|
||||
margin-bottom: 3px !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-ws-card-icon img {
|
||||
width: 26px !important;
|
||||
height: 26px !important;
|
||||
object-fit: contain !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-ws-card-title {
|
||||
font-size: 14.5px !important;
|
||||
font-weight: 600 !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-ws-card-url {
|
||||
color: #64748b !important;
|
||||
font-size: 12px !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
/* ── 工作空间:侧栏与内容区同样走玻璃圆角 ── */
|
||||
html[data-theme] nav.side-bar {
|
||||
background: rgba(255, 255, 255, .62) !important;
|
||||
@ -585,6 +929,11 @@ html[data-theme] .navi-search-submit:hover {
|
||||
box-shadow: 0 12px 26px rgba(37, 99, 235, .34) !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-search-submit:focus,
|
||||
html[data-theme] .navi-search-submit:active {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
html[data-theme] .navi-search-submit:active {
|
||||
transform: translateY(0) scale(.96);
|
||||
}
|
||||
|
||||
209
web/navi.js
209
web/navi.js
@ -28,9 +28,12 @@
|
||||
return el;
|
||||
}
|
||||
|
||||
// 收起态是放大镜,展开时通过 transform/opacity 平滑形变为向左箭头,
|
||||
// 让图标动效与搜索框的宽度动画保持一致。
|
||||
const magnifierIcon = () => svg([
|
||||
{ tag: 'circle', attrs: { cx: '11', cy: '11', r: '7.5' } },
|
||||
{ tag: 'line', attrs: { x1: '20.5', y1: '20.5', x2: '16.2', y2: '16.2' } },
|
||||
{ tag: 'circle', attrs: { class: 'navi-magnifier-ring', cx: '11', cy: '11', r: '7.5' } },
|
||||
{ tag: 'line', attrs: { class: 'navi-magnifier-handle', x1: '20.5', y1: '20.5', x2: '16.2', y2: '16.2' } },
|
||||
{ tag: 'polyline', attrs: { class: 'navi-collapse-arrow', points: '14.5 6.5 9 12 14.5 17.5' } },
|
||||
], { size: '20' });
|
||||
|
||||
const arrowIcon = () => svg([
|
||||
@ -134,6 +137,205 @@
|
||||
document.body.append(link);
|
||||
}
|
||||
|
||||
// 视图切换:Dashy 原生的切换按钮挤在侧栏底部、且是方形图标。
|
||||
// 这里换成圆形悬浮按钮,紧挨管理入口左侧,点开是一组视图链接。
|
||||
const VIEWS = [
|
||||
{ path: '/', label: '首页' },
|
||||
{ path: '/minimal', label: '极简' },
|
||||
{ path: '/workspace', label: '工作空间' },
|
||||
];
|
||||
|
||||
const viewIcon = () => svg([
|
||||
{ tag: 'rect', attrs: { x: '3', y: '3.5', width: '18', height: '17', rx: '3.4' } },
|
||||
{ tag: 'line', attrs: { x1: '9.4', y1: '3.5', x2: '9.4', y2: '20.5' } },
|
||||
{ tag: 'line', attrs: { x1: '12.8', y1: '9.4', x2: '17.8', y2: '9.4' } },
|
||||
{ tag: 'line', attrs: { x1: '12.8', y1: '14.2', x2: '17.8', y2: '14.2' } },
|
||||
], { size: '20', width: '1.9' });
|
||||
|
||||
function currentView() {
|
||||
const p = location.pathname.replace(/\/+$/, '') || '/';
|
||||
if (p.endsWith('/minimal')) return '/minimal';
|
||||
if (p.endsWith('/workspace')) return '/workspace';
|
||||
return '/';
|
||||
}
|
||||
|
||||
function mountViewSwitch() {
|
||||
if (document.querySelector('.navi-view-switch')) return;
|
||||
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'navi-view-switch';
|
||||
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'navi-view-toggle';
|
||||
btn.setAttribute('aria-label', '切换视图');
|
||||
btn.setAttribute('aria-expanded', 'false');
|
||||
btn.append(viewIcon());
|
||||
|
||||
const menu = document.createElement('div');
|
||||
menu.className = 'navi-view-menu';
|
||||
menu.hidden = true;
|
||||
const active = currentView();
|
||||
VIEWS.forEach((v) => {
|
||||
const a = document.createElement('a');
|
||||
a.className = 'navi-view-option';
|
||||
a.href = v.path;
|
||||
a.textContent = v.label;
|
||||
if (v.path === active) {
|
||||
a.classList.add('is-active');
|
||||
a.setAttribute('aria-current', 'page');
|
||||
}
|
||||
menu.append(a);
|
||||
});
|
||||
|
||||
const close = () => {
|
||||
menu.hidden = true;
|
||||
btn.setAttribute('aria-expanded', 'false');
|
||||
};
|
||||
|
||||
btn.addEventListener('click', (evt) => {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
const open = menu.hidden;
|
||||
menu.hidden = !open;
|
||||
btn.setAttribute('aria-expanded', String(open));
|
||||
});
|
||||
|
||||
document.addEventListener('click', (evt) => {
|
||||
if (!menu.hidden && !wrap.contains(evt.target)) close();
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (evt) => {
|
||||
if (evt.key === 'Escape' && !menu.hidden) close();
|
||||
});
|
||||
|
||||
wrap.append(btn, menu);
|
||||
document.body.append(wrap);
|
||||
}
|
||||
|
||||
// Dashy 的「工作空间」右侧面板是一个 iframe:侧栏点应用时把目标 URL
|
||||
// 内嵌进去。但多数应用(Gitea、VanBlog 等)用 X-Frame-Options 拒绝被嵌,
|
||||
// 面板就是一片空白(实测:Refused to display … 'sameorigin')。
|
||||
// 所以这里改成:点左侧分组 → 右侧列出该分组的应用卡片,不依赖内嵌。
|
||||
const WS_ICON_RE = /\.(png|svg|jpe?g|webp|gif|ico)$/i;
|
||||
let wsSections = null;
|
||||
let wsPending = false;
|
||||
|
||||
async function fetchWorkspaceSections() {
|
||||
if (wsSections) return wsSections;
|
||||
if (wsPending) return null;
|
||||
wsPending = true;
|
||||
try {
|
||||
const res = await fetch('/admin/api/sections', { credentials: 'same-origin' });
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json();
|
||||
const list = Array.isArray(data) ? data : (data.sections || []);
|
||||
// 与 Dashy 侧栏保持同序:剔除标记了 hideFromWorkspace 的分组。
|
||||
wsSections = list.filter((s) => !(s && s.displayData && s.displayData.hideFromWorkspace));
|
||||
return wsSections;
|
||||
} catch (err) {
|
||||
return null;
|
||||
} finally {
|
||||
wsPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
function workspacePane() {
|
||||
const host = document.querySelector('.work-space');
|
||||
if (!host) return null;
|
||||
let pane = host.querySelector('.navi-ws-pane');
|
||||
if (!pane) {
|
||||
pane = document.createElement('div');
|
||||
pane.className = 'navi-ws-pane';
|
||||
host.append(pane);
|
||||
}
|
||||
return pane;
|
||||
}
|
||||
|
||||
function renderWorkspaceSection(section) {
|
||||
const pane = workspacePane();
|
||||
if (!pane || !section) return;
|
||||
while (pane.firstChild) pane.removeChild(pane.firstChild);
|
||||
|
||||
const title = document.createElement('h2');
|
||||
title.className = 'navi-ws-title';
|
||||
title.textContent = section.name || '';
|
||||
pane.append(title);
|
||||
|
||||
const items = (section.items || []).filter((it) => it && it.url);
|
||||
if (!items.length) {
|
||||
const empty = document.createElement('div');
|
||||
empty.className = 'navi-ws-hint';
|
||||
empty.textContent = '这个分组还没有项目';
|
||||
pane.append(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'navi-ws-grid';
|
||||
items.forEach((it) => {
|
||||
const card = document.createElement('a');
|
||||
card.className = 'navi-ws-card';
|
||||
card.href = it.url;
|
||||
card.target = '_blank';
|
||||
card.rel = 'noopener noreferrer';
|
||||
|
||||
const icon = String(it.icon || '');
|
||||
if (WS_ICON_RE.test(icon)) {
|
||||
const wrap = document.createElement('span');
|
||||
wrap.className = 'navi-ws-card-icon';
|
||||
const img = document.createElement('img');
|
||||
img.src = '/item-icons/' + icon;
|
||||
img.alt = '';
|
||||
img.loading = 'lazy';
|
||||
wrap.append(img);
|
||||
card.append(wrap);
|
||||
}
|
||||
|
||||
const name = document.createElement('span');
|
||||
name.className = 'navi-ws-card-title';
|
||||
name.textContent = it.title || it.url;
|
||||
card.append(name);
|
||||
|
||||
const sub = document.createElement('span');
|
||||
sub.className = 'navi-ws-card-url';
|
||||
sub.textContent = String(it.description || '').trim()
|
||||
|| it.url.replace(/^https?:\/\//, '').replace(/\/$/, '');
|
||||
card.append(sub);
|
||||
|
||||
grid.append(card);
|
||||
});
|
||||
pane.append(grid);
|
||||
}
|
||||
|
||||
function enableWorkspacePane() {
|
||||
if (window.__naviWorkspacePane) return;
|
||||
window.__naviWorkspacePane = true;
|
||||
|
||||
document.addEventListener('click', async (evt) => {
|
||||
if (!/^\/workspace/.test(location.pathname)) return;
|
||||
const target = evt.target;
|
||||
const head = target && target.closest ? target.closest('.side-bar-item-container') : null;
|
||||
if (!head) return;
|
||||
const sections = await fetchWorkspaceSections();
|
||||
if (!sections || !sections.length) return;
|
||||
const heads = Array.from(document.querySelectorAll('nav.side-bar .side-bar-item-container'));
|
||||
const idx = heads.indexOf(head);
|
||||
if (idx >= 0 && sections[idx]) renderWorkspaceSection(sections[idx]);
|
||||
});
|
||||
}
|
||||
|
||||
// 工作空间初次打开时先把第一个分组的内容摆上,避免右侧一片空白。
|
||||
let wsSeeded = false;
|
||||
async function seedWorkspacePane() {
|
||||
if (wsSeeded) return;
|
||||
if (!/^\/workspace/.test(location.pathname)) return;
|
||||
if (!document.querySelector('.work-space')) return;
|
||||
wsSeeded = true;
|
||||
const sections = await fetchWorkspaceSections();
|
||||
if (sections && sections.length) renderWorkspaceSection(sections[0]);
|
||||
}
|
||||
|
||||
// 极简视图(/minimal)与工作空间视图不渲染 Dashy 页脚,备案号会整段消失。
|
||||
// 这里从只读的 pageinfo 取回页脚 HTML 补一份,保证每个视图都能看到备案信息。
|
||||
let footerPending = false;
|
||||
@ -166,6 +368,9 @@
|
||||
function run() {
|
||||
mountSearch();
|
||||
mountAdminEntry();
|
||||
mountViewSwitch();
|
||||
enableWorkspacePane();
|
||||
seedWorkspacePane();
|
||||
hideInstallEntry();
|
||||
mountFooter();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user