diff --git a/public/oa-drive/app.css b/public/oa-drive/app.css index 45036c4..f2cc884 100644 --- a/public/oa-drive/app.css +++ b/public/oa-drive/app.css @@ -1,21 +1,86 @@ -:root { - --bg: #0f1117; - --panel: #171a22; - --border: #2a3140; - --text: #e8ecf4; - --muted: #8b95a8; - --accent: #5b8cff; +:root, +[data-theme='light'] { + color-scheme: light; + --bg: #f4f1ea; + --bg-gradient-start: #fffaf0; + --panel: #fffaf0; + --border: rgba(24, 33, 29, 0.12); + --text: #18211d; + --muted: #52605a; + --accent: #2f6f57; + --accent-2: #2f6f57; + --danger: #c4453d; + --drop: rgba(47, 111, 87, 0.12); + --warning: #eeb04e; + --overlay: rgba(244, 241, 234, 0.72); + --finder-bg: #fffaf0; + --finder-bg-head: #f0ebe0; + --finder-bg-body: #ffffff; + --finder-border: rgba(24, 33, 29, 0.14); + --finder-text: #18211d; + --finder-muted: #52605a; + --finder-subtle: #7a8780; + --finder-surface: #ffffff; + --finder-accent: #2f6f57; + --finder-hover: rgba(47, 111, 87, 0.08); + --finder-selected: rgba(47, 111, 87, 0.16); + --finder-drop: rgba(47, 111, 87, 0.12); + --finder-danger: #c4453d; + --finder-input-border: rgba(24, 33, 29, 0.18); + --finder-focus-ring: rgba(47, 111, 87, 0.15); + --finder-accent-ghost: rgba(47, 111, 87, 0.1); + --finder-chip-active-bg: rgba(47, 111, 87, 0.12); + --finder-chip-active-border: rgba(47, 111, 87, 0.35); + --finder-resizer: #7a8780; + --finder-row-border: rgba(24, 33, 29, 0.06); + --finder-bc-bg: #f4f1ea; + --finder-empty-bg: #faf8f4; +} + +[data-theme='dark'] { + color-scheme: dark; + --bg: #0f1419; + --bg-gradient-start: #1a2236; + --panel: #121820; + --border: #243041; + --text: #e7ecf3; + --muted: #8fa3bf; + --accent: #3d8bfd; --accent-2: #3dd68c; --danger: #ff6b7a; --drop: rgba(91, 140, 255, 0.12); --warning: #ffb347; + --overlay: rgba(15, 17, 23, 0.72); + --finder-bg: #121820; + --finder-bg-head: #1c2735; + --finder-bg-body: #0f1419; + --finder-border: #243041; + --finder-text: #e7ecf3; + --finder-muted: #8fa3bf; + --finder-subtle: #6b829e; + --finder-surface: #1c2735; + --finder-accent: #3d8bfd; + --finder-hover: rgba(61, 139, 253, 0.12); + --finder-selected: rgba(61, 139, 253, 0.22); + --finder-drop: rgba(61, 139, 253, 0.18); + --finder-danger: #ff6b7a; + --finder-input-border: #2a3a50; + --finder-focus-ring: rgba(61, 139, 253, 0.25); + --finder-accent-ghost: rgba(61, 139, 253, 0.1); + --finder-chip-active-bg: rgba(61, 139, 253, 0.15); + --finder-chip-active-border: rgba(61, 139, 253, 0.45); + --finder-resizer: #6b829e; + --finder-row-border: #243041; + --finder-bc-bg: #1c2735; + --finder-empty-bg: #121820; } + * { box-sizing: border-box; } body { margin: 0; min-height: 100vh; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - background: radial-gradient(circle at top, #1a2236 0%, var(--bg) 45%); + background: radial-gradient(circle at top, var(--bg-gradient-start) 0%, var(--bg) 45%); color: var(--text); } .wrap { max-width: min(1280px, 96vw); margin: 0 auto; padding: 10px 16px 20px; } @@ -55,7 +120,7 @@ body { white-space: nowrap; flex: none; } .link-chip:hover { border-color: var(--accent); } -.link-chip.active { border-color: var(--accent); background: #e8f0fe; color: #0066cc; font-weight: 600; } +.link-chip.active { border-color: var(--accent); background: var(--finder-chip-active-bg); color: var(--finder-accent); font-weight: 600; } .top-actions { display: flex; gap: 4px; flex: none; } .top-actions button { padding: 3px 8px; font-size: 11px; min-height: 0; line-height: 1.3; } .top-actions button.primary { padding: 3px 9px; } @@ -129,7 +194,7 @@ button:disabled { opacity: 0.45; cursor: not-allowed; filter: none; } .drag-overlay { position: fixed; inset: 0; z-index: 200; pointer-events: none; display: none; align-items: center; justify-content: center; - background: rgba(15, 17, 23, 0.72); backdrop-filter: blur(4px); + background: var(--overlay); backdrop-filter: blur(4px); } .drag-overlay.show { display: flex; pointer-events: none; } .drag-overlay-box { @@ -144,8 +209,8 @@ button:disabled { opacity: 0.45; cursor: not-allowed; filter: none; } .panel-shell { margin-top: 8px; position: relative; width: 100%; max-width: 100%; } .panel { display: flex; flex-direction: column; - background: #f5f5f7; border: 1px solid #d1d1d6; - border-radius: 10px; overflow: hidden; color: #1d1d1f; + background: var(--finder-bg); border: 1px solid var(--finder-border); + border-radius: 10px; overflow: hidden; color: var(--finder-text); height: 520px; min-height: 280px; max-height: 85vh; } .panel-resizer { @@ -154,57 +219,57 @@ button:disabled { opacity: 0.45; cursor: not-allowed; filter: none; } } .panel-resizer::after { content: ''; position: absolute; right: 4px; bottom: 4px; width: 10px; height: 10px; - border-right: 2px solid #86868b; border-bottom: 2px solid #86868b; + border-right: 2px solid var(--finder-subtle); border-bottom: 2px solid var(--finder-subtle); border-radius: 0 0 2px 0; opacity: 0.75; transition: opacity .15s; } .panel-resizer:hover::after, -.panel-shell-resizing .panel-resizer::after { opacity: 1; border-color: #0066cc; } +.panel-shell-resizing .panel-resizer::after { opacity: 1; border-color: var(--finder-accent); } body.panel-shell-resizing { user-select: none; cursor: nwse-resize; } /* Toolbar */ .panel-head { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; - padding: 10px 14px; border-bottom: 1px solid #d1d1d6; font-weight: 600; - font-size: 13px; background: #ebebed; flex: none; + padding: 10px 14px; border-bottom: 1px solid var(--finder-border); font-weight: 600; + font-size: 13px; background: var(--finder-bg-head); flex: none; } -.panel-head small { color: #6e6e73; font-weight: 400; } +.panel-head small { color: var(--finder-muted); font-weight: 400; } .panel-head button { - background: #fff; border-color: #c7c7cc; color: #1d1d1f; padding: 4px 10px; + background: var(--finder-surface); border-color: var(--finder-input-border); color: var(--finder-text); padding: 4px 10px; border-radius: 6px; font-size: 12px; } .panel-head .bulk-actions { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; } .panel-head .select-all-label { display: inline-flex; align-items: center; gap: 4px; font-weight: 400; - font-size: 12px; color: #6e6e73; cursor: pointer; user-select: none; + font-size: 12px; color: var(--finder-muted); cursor: pointer; user-select: none; } -.panel-head .select-all-label input { width: 14px; height: 14px; accent-color: #0066cc; cursor: pointer; } +.panel-head .select-all-label input { width: 14px; height: 14px; accent-color: var(--finder-accent); cursor: pointer; } .panel-head .bulk-actions button.danger { - background: #fff; border-color: #ff3b30; color: #ff3b30; + background: var(--finder-surface); border-color: var(--finder-danger); color: var(--finder-danger); } .panel-head .bulk-actions button.danger:disabled { opacity: 0.45; cursor: not-allowed; } -.panel-head .bulk-actions button.danger:not(:disabled):hover { background: #ff3b30; color: #fff; } +.panel-head .bulk-actions button.danger:not(:disabled):hover { background: var(--finder-danger); color: #fff; } /* Search bar */ .toolbar-search { display: flex; align-items: center; gap: 6px; flex: 1; min-width: 140px; max-width: 220px; } .toolbar-search input { - flex: 1; min-width: 0; padding: 4px 8px; border: 1px solid #c7c7cc; border-radius: 6px; - font-size: 12px; background: #fff; color: #1d1d1f; + flex: 1; min-width: 0; padding: 4px 8px; border: 1px solid var(--finder-input-border); border-radius: 6px; + font-size: 12px; background: var(--finder-surface); color: var(--finder-text); } -.toolbar-search input:focus { outline: none; border-color: #0066cc; box-shadow: 0 0 0 2px rgba(0,102,204,.15); } -.toolbar-search input::placeholder { color: #aeaeb2; } +.toolbar-search input:focus { outline: none; border-color: var(--finder-accent); box-shadow: 0 0 0 2px var(--finder-focus-ring); } +.toolbar-search input::placeholder { color: var(--finder-subtle); } /* Conflict policy select */ .conflict-select { - padding: 4px 6px; border: 1px solid #c7c7cc; border-radius: 6px; - font-size: 11px; background: #fff; color: #1d1d1f; cursor: pointer; + padding: 4px 6px; border: 1px solid var(--finder-input-border); border-radius: 6px; + font-size: 11px; background: var(--finder-surface); color: var(--finder-text); cursor: pointer; } -.conflict-select:focus { outline: none; border-color: #0066cc; } +.conflict-select:focus { outline: none; border-color: var(--finder-accent); } /* Notification toggle */ .notify-btn { position: relative; } -.notify-btn.active { background: #e8f0fe; border-color: #0066cc; color: #0066cc; } +.notify-btn.active { background: var(--finder-hover); border-color: var(--finder-accent); color: var(--finder-accent); } .notify-btn .badge { position: absolute; top: -4px; right: -4px; width: 8px; height: 8px; border-radius: 50%; background: var(--accent-2); display: none; @@ -212,29 +277,29 @@ body.panel-shell-resizing { user-select: none; cursor: nwse-resize; } .notify-btn.has-unread .badge { display: block; } .view-toggle { - display: inline-flex; border: 1px solid #c7c7cc; border-radius: 6px; overflow: hidden; background: #fff; + display: inline-flex; border: 1px solid var(--finder-input-border); border-radius: 6px; overflow: hidden; background: var(--finder-surface); } .view-toggle button { - border: none; border-radius: 0; padding: 4px 8px; min-height: 0; background: #fff; color: #6e6e73; + border: none; border-radius: 0; padding: 4px 8px; min-height: 0; background: var(--finder-surface); color: var(--finder-muted); font-size: 14px; line-height: 1; } -.view-toggle button + button { border-left: 1px solid #c7c7cc; } -.view-toggle button.active { background: #e8f0fe; color: #0066cc; } -.view-toggle button:hover { filter: none; background: #f2f2f7; } -.view-toggle button.active:hover { background: #e8f0fe; } +.view-toggle button + button { border-left: 1px solid var(--finder-input-border); } +.view-toggle button.active { background: var(--finder-hover); color: var(--finder-accent); } +.view-toggle button:hover { filter: none; background: var(--finder-hover); } +.view-toggle button.active:hover { background: var(--finder-hover); } .breadcrumb { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; - padding: 8px 14px; border-bottom: 1px solid #d1d1d6; background: #fafafa; - font-size: 12px; color: #6e6e73; flex: none; + padding: 8px 14px; border-bottom: 1px solid var(--finder-border); background: var(--finder-bc-bg); + font-size: 12px; color: var(--finder-muted); flex: none; } .breadcrumb .bc-link { - background: none; border: none; padding: 0; color: #0066cc; cursor: pointer; + background: none; border: none; padding: 0; color: var(--finder-accent); cursor: pointer; font-size: 12px; border-radius: 0; min-height: 0; } .breadcrumb .bc-link:hover { text-decoration: underline; filter: none; } -.breadcrumb .bc-sep { color: #aeaeb2; user-select: none; } -.breadcrumb .bc-current { color: #1d1d1f; font-weight: 600; } +.breadcrumb .bc-sep { color: var(--finder-subtle); user-select: none; } +.breadcrumb .bc-current { color: var(--finder-text); font-weight: 600; } /* File list */ .file-list { @@ -252,30 +317,30 @@ body.panel-shell-resizing { user-select: none; cursor: nwse-resize; } } .list-header .col-check input, .list-row .col-check input, .list-row .col-check { - width: 14px; height: 14px; cursor: pointer; accent-color: #0066cc; + width: 14px; height: 14px; cursor: pointer; accent-color: var(--finder-accent); } .list-row .col-check { width: 28px; margin: 0; } .list-header { - height: 28px; background: #ebebed; border-bottom: 1px solid #d1d1d6; - color: #6e6e73; font-size: 12px; user-select: none; flex: none; + height: 28px; background: var(--finder-bg-head); border-bottom: 1px solid var(--finder-border); + color: var(--finder-muted); font-size: 12px; user-select: none; flex: none; } .list-header .sortable { cursor: pointer; display: inline-flex; align-items: center; gap: 4px; } -.list-header .sortable:hover { color: #1d1d1f; } -.list-header .sortable.active { color: #1d1d1f; font-weight: 600; } +.list-header .sortable:hover { color: var(--finder-text); } +.list-header .sortable.active { color: var(--finder-text); font-weight: 600; } .list-header .col-size, .list-row .col-size { text-align: right; font-variant-numeric: tabular-nums; } -.list-header .col-date, .list-row .col-date { color: #6e6e73; font-variant-numeric: tabular-nums; white-space: nowrap; } +.list-header .col-date, .list-row .col-date { color: var(--finder-muted); font-variant-numeric: tabular-nums; white-space: nowrap; } .list-body { - background: #fff; flex: 1; min-height: 0; overflow: auto; position: relative; + background: var(--finder-bg-body); flex: 1; min-height: 0; overflow: auto; position: relative; } -.list-body.drop-target { background: #d6e8ff; outline: 1px dashed #0066cc; outline-offset: -2px; } +.list-body.drop-target { background: var(--finder-drop); outline: 1px dashed var(--finder-accent); outline-offset: -2px; } .list-row { - min-height: 28px; border-bottom: 1px solid #ececf0; position: relative; + min-height: 28px; border-bottom: 1px solid var(--finder-row-border); position: relative; } .list-row:last-child { border-bottom: none; } -.list-row:hover { background: #e8f0fe; } -.list-row.selected { background: #cce4ff; } +.list-row:hover { background: var(--finder-hover); } +.list-row.selected { background: var(--finder-selected); } .list-row.folder { cursor: default; } .list-row[draggable="true"], .icon-item[draggable="true"] { cursor: grab; } .list-row.dragging, .icon-item.dragging { opacity: 0.45; } @@ -283,24 +348,24 @@ body.panel-shell-resizing { user-select: none; cursor: nwse-resize; } display: flex; align-items: center; gap: 6px; min-width: 0; } .list-row .chevron { - width: 12px; flex: none; color: #86868b; font-size: 10px; text-align: center; + width: 12px; flex: none; color: var(--finder-subtle); font-size: 10px; text-align: center; } .list-row .chevron.hidden { visibility: hidden; } /* Origin badge */ .origin-badge { flex: none; font-size: 9px; padding: 1px 5px; border-radius: 999px; - background: #f2f2f7; border: 1px solid #d1d1d6; color: #6e6e73; + background: var(--finder-hover); border: 1px solid var(--finder-border); color: var(--finder-muted); max-width: 72px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.origin-badge.self { background: #e8f0fe; border-color: #99caff; color: #0066cc; } +.origin-badge.self { background: var(--finder-hover); border-color: var(--finder-chip-active-border); color: var(--finder-accent); } /* Inline rename */ .rename-input { - flex: 1; min-width: 0; padding: 1px 4px; border: 1px solid #0066cc; border-radius: 4px; - font-size: 13px; font-family: inherit; background: #fff; color: #1d1d1f; + flex: 1; min-width: 0; padding: 1px 4px; border: 1px solid var(--finder-accent); border-radius: 4px; + font-size: 13px; font-family: inherit; background: var(--finder-surface); color: var(--finder-text); } -.rename-input:focus { outline: none; box-shadow: 0 0 0 2px rgba(0,102,204,.2); } +.rename-input:focus { outline: none; box-shadow: 0 0 0 2px var(--finder-focus-ring); } .file-icon { width: 16px; height: 16px; flex: none; background-size: contain; background-repeat: no-repeat; @@ -325,51 +390,51 @@ body.panel-shell-resizing { user-select: none; cursor: nwse-resize; } background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect fill='%235b8cff' x='2' y='3' width='12' height='10' rx='1.5'/%3E%3Cpath fill='%23fff' d='M7 6.5v3l3-1.5z'/%3E%3C/svg%3E"); } .list-row .name-link { - color: #1d1d1f; text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + color: var(--finder-text); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.list-row .name-link.previewable:hover { color: #0066cc; text-decoration: underline; } +.list-row .name-link.previewable:hover { color: var(--finder-accent); text-decoration: underline; } .list-row .name-text { - overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #1d1d1f; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--finder-text); } -.list-row .col-size { color: #6e6e73; } +.list-row .col-size { color: var(--finder-muted); } .list-row .col-actions { display: flex; justify-content: flex-end; gap: 4px; opacity: 0; } .list-row:hover .col-actions { opacity: 1; } .list-row .col-actions button, .list-row .col-actions a { - background: transparent; border: none; color: #0066cc; padding: 2px 4px; + background: transparent; border: none; color: var(--finder-accent); padding: 2px 4px; font-size: 12px; border-radius: 4px; min-height: 0; } .list-row .col-actions button:hover, .list-row .col-actions a:hover { - background: rgba(0,102,204,.1); filter: none; + background: var(--finder-accent-ghost); filter: none; } -.list-row .col-actions button[data-del] { color: #ff3b30; } -.list-row .col-actions button[data-info] { color: #6e6e73; } +.list-row .col-actions button[data-del] { color: var(--finder-danger); } +.list-row .col-actions button[data-info] { color: var(--finder-muted); } /* Empty + empty folder drop zone */ .empty { - padding: 40px 18px; text-align: center; color: #86868b; background: #fff; font-size: 13px; + padding: 40px 18px; text-align: center; color: var(--finder-subtle); background: var(--finder-bg-body); font-size: 13px; } .empty-drop-zone { margin: 24px auto; max-width: 360px; padding: 32px 20px; - border: 2px dashed #c7c7cc; border-radius: 12px; background: #fafafa; - color: #6e6e73; transition: .15s; + border: 2px dashed var(--finder-input-border); border-radius: 12px; background: var(--finder-bc-bg); + color: var(--finder-muted); transition: .15s; } .empty-drop-zone.dragover { - border-color: #0066cc; background: #d6e8ff; color: #0066cc; + border-color: var(--finder-accent); background: var(--finder-drop); color: var(--finder-accent); } .empty-drop-zone .icon { font-size: 32px; margin-bottom: 8px; } /* Load more */ -.load-more-wrap { padding: 10px; text-align: center; background: #fff; border-top: 1px solid #ececf0; } +.load-more-wrap { padding: 10px; text-align: center; background: var(--finder-bg-body); border-top: 1px solid var(--finder-row-border); } .load-more-btn { - background: #fff; border: 1px solid #c7c7cc; color: #0066cc; padding: 6px 16px; + background: var(--finder-surface); border: 1px solid var(--finder-input-border); color: var(--finder-accent); padding: 6px 16px; border-radius: 6px; font-size: 12px; cursor: pointer; } -.load-more-btn:hover { background: #e8f0fe; filter: none; } +.load-more-btn:hover { background: var(--finder-hover); filter: none; } input[type=file] { display: none; } -.list-row.drop-target { background: #d6e8ff !important; } +.list-row.drop-target { background: var(--finder-drop) !important; } /* Icon mode */ .file-list.icon-mode .list-header { display: none; } @@ -383,16 +448,16 @@ input[type=file] { display: none; } border: 1px solid transparent; min-width: 0; text-align: center; } .icon-item.folder { cursor: default; } -.icon-item:hover { background: #e8f0fe; border-color: #d1e3ff; } -.icon-item.selected { background: #cce4ff; border-color: #99caff; } -.icon-item.drop-target { background: #d6e8ff !important; border-color: #0066cc; } +.icon-item:hover { background: var(--finder-hover); border-color: var(--finder-chip-active-border); } +.icon-item.selected { background: var(--finder-selected); border-color: var(--finder-chip-active-border); } +.icon-item.drop-target { background: var(--finder-drop) !important; border-color: var(--finder-accent); } .icon-item .item-check-wrap { position: absolute; top: 4px; left: 4px; z-index: 2; display: flex; align-items: center; justify-content: center; width: 22px; height: 22px; cursor: pointer; } .icon-item .item-check { - width: 14px; height: 14px; accent-color: #0066cc; cursor: pointer; margin: 0; + width: 14px; height: 14px; accent-color: var(--finder-accent); cursor: pointer; margin: 0; } .icon-item .icon-thumb { width: 56px; height: 56px; display: flex; align-items: center; justify-content: center; flex: none; @@ -400,24 +465,24 @@ input[type=file] { display: none; } .icon-item .icon-thumb .file-icon { width: 48px; height: 48px; } .icon-item .icon-thumb-img { width: 56px; height: 56px; object-fit: cover; border-radius: 6px; - border: 1px solid #ececf0; background: #fafafa; + border: 1px solid var(--finder-row-border); background: var(--finder-bc-bg); } .icon-item .icon-name { - width: 100%; font-size: 11px; line-height: 1.35; color: #1d1d1f; + width: 100%; font-size: 11px; line-height: 1.35; color: var(--finder-text); overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; word-break: break-all; } .icon-item .icon-name a { color: inherit; text-decoration: none; } -.icon-item .icon-name a.previewable:hover { color: #0066cc; text-decoration: underline; } -.icon-item .icon-meta { font-size: 10px; color: #86868b; line-height: 1.2; } +.icon-item .icon-name a.previewable:hover { color: var(--finder-accent); text-decoration: underline; } +.icon-item .icon-meta { font-size: 10px; color: var(--finder-subtle); line-height: 1.2; } .icon-item .icon-actions { display: flex; gap: 6px; opacity: 0; font-size: 11px; } .icon-item:hover .icon-actions { opacity: 1; } .icon-item .icon-actions a, .icon-item .icon-actions button { - background: none; border: none; color: #0066cc; padding: 0; font-size: 11px; min-height: 0; + background: none; border: none; color: var(--finder-accent); padding: 0; font-size: 11px; min-height: 0; } -.icon-item .icon-actions button[data-del] { color: #ff3b30; } +.icon-item .icon-actions button[data-del] { color: var(--finder-danger); } /* Modals (shared) */ .modal { diff --git a/public/oa-drive/app.js b/public/oa-drive/app.js index 73c7f1a..6cb3d94 100644 --- a/public/oa-drive/app.js +++ b/public/oa-drive/app.js @@ -1,6 +1,59 @@ (() => { 'use strict'; + const THEME_STORAGE_KEY = 'memind-theme'; + + function getStoredTheme() { + const stored = localStorage.getItem(THEME_STORAGE_KEY); + return stored === 'dark' ? 'dark' : 'light'; + } + + function applyTheme(theme) { + const normalized = theme === 'dark' ? 'dark' : 'light'; + document.documentElement.dataset.theme = normalized; + document.documentElement.style.colorScheme = normalized; + } + + function updateThemeToggleLabel() { + const themeToggle = document.getElementById('themeToggle'); + if (!themeToggle) return; + themeToggle.textContent = getStoredTheme() === 'light' ? '深色' : '浅色'; + themeToggle.title = getStoredTheme() === 'light' ? '切换深色模式' : '切换浅色模式'; + } + + function toggleTheme() { + const next = getStoredTheme() === 'light' ? 'dark' : 'light'; + localStorage.setItem(THEME_STORAGE_KEY, next); + applyTheme(next); + updateThemeToggleLabel(); + window.dispatchEvent(new CustomEvent('memind-theme-change', { detail: { theme: next } })); + return next; + } + + function initTheme() { + applyTheme(getStoredTheme()); + updateThemeToggleLabel(); + window.addEventListener('storage', (event) => { + if (event.key === THEME_STORAGE_KEY) { + applyTheme(getStoredTheme()); + updateThemeToggleLabel(); + } + }); + window.addEventListener('memind-theme-change', (event) => { + const detail = event.detail; + if (detail?.theme) { + applyTheme(detail.theme); + updateThemeToggleLabel(); + } + }); + const themeToggle = document.getElementById('themeToggle'); + if (themeToggle) { + themeToggle.addEventListener('click', () => toggleTheme()); + } + } + + initTheme(); + const API_BASE = '/api/mindspace/v1/oa/drive'; const fetchOpts = { credentials: 'same-origin' }; @@ -90,6 +143,7 @@ let pendingConflictResolve = null; let uploadAbort = false; let filesHydrated = false; + let currentUser = null; let panelSize = (() => { try { @@ -172,6 +226,45 @@ statusEl.className = 'status ' + type; } + function redirectToLogin() { + const returnTo = encodeURIComponent('/space/oa/drive'); + const target = window.top && window.top !== window ? window.top : window; + target.location.href = `/?return_to=${returnTo}`; + } + + async function ensureAuthenticated() { + const res = await fetch('/auth/status', fetchOpts); + if (!res.ok) { + redirectToLogin(); + return null; + } + const data = await res.json(); + if (!data.authenticated || !data.user?.id) { + redirectToLogin(); + return null; + } + return data.user; + } + + function renderUserBanner(user, quota) { + if (!linksEl || !user) return; + const name = user.displayName || user.username || user.slug || '用户'; + const chips = [`${escapeHtml(name)} 的 OA 网盘`]; + if (quota) { + chips.push(`剩余 ${fmtSize(quota.availableBytes)} / ${fmtSize(quota.quotaBytes)}`); + } + linksEl.innerHTML = chips.join(''); + } + + async function driveFetch(url, opts = {}) { + const res = await fetch(url, { ...fetchOpts, ...opts }); + if (res.status === 401 || res.status === 403) { + redirectToLogin(); + throw new Error('未登录'); + } + return res; + } + function viewUrl(name) { return API_BASE + '/view/' + encodeURIComponent(name).replace(/%2F/g, '/'); } @@ -734,23 +827,16 @@ /* ── API: load info ── */ async function loadInfo() { - const res = await fetch(API_BASE + '/info', fetchOpts); + const res = await driveFetch(API_BASE + '/info'); if (!res.ok) throw new Error(`HTTP ${res.status}`); serverInfo = await res.json(); displayLimit = serverInfo.pageSize || 100; - if (linksEl) { - const quota = serverInfo.quota; - if (quota) { - linksEl.innerHTML = `剩余 ${fmtSize(quota.availableBytes)} / ${fmtSize(quota.quotaBytes)}`; - } else { - linksEl.innerHTML = ''; - } - } + renderUserBanner(currentUser, serverInfo.quota); } async function refreshFiles() { try { - const res = await fetch(API_BASE + '/files', fetchOpts); + const res = await driveFetch(API_BASE + '/files'); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); applyFilePayload({ ...data, delta: false }); @@ -1754,6 +1840,11 @@ initPanelResize(); setViewMode(viewMode); void (async () => { + currentUser = await ensureAuthenticated(); + if (!currentUser) { + setStatus('未登录,正在跳转…', 'err'); + return; + } await loadInfo(); await refreshFiles(); connectEvents(); diff --git a/public/oa-drive/index.html b/public/oa-drive/index.html index cb4c54d..ca6fad1 100644 --- a/public/oa-drive/index.html +++ b/public/oa-drive/index.html @@ -14,6 +14,7 @@ 云端同步 · 拖放/⌘V粘贴上传