feat(portal): add light/dark theme sync and OA drive navigation
Memind CI / Test, build, and release guards (push) Successful in 4m31s
Memind CI / Test, build, and release guards (push) Successful in 4m31s
Unify chat and OA drive appearance with shared memind-theme storage, default light mode, and cross-frame sync so users can switch themes from chat or the drive shell. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+149
-84
@@ -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 {
|
||||
|
||||
+101
-10
@@ -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 = [`<span class="link-chip active">${escapeHtml(name)} 的 OA 网盘</span>`];
|
||||
if (quota) {
|
||||
chips.push(`<span class="link-chip">剩余 ${fmtSize(quota.availableBytes)} / ${fmtSize(quota.quotaBytes)}</span>`);
|
||||
}
|
||||
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 = `<span class="link-chip active">剩余 ${fmtSize(quota.availableBytes)} / ${fmtSize(quota.quotaBytes)}</span>`;
|
||||
} 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();
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<span class="top-sub">云端同步 · 拖放/<kbd>⌘V</kbd>粘贴上传</span>
|
||||
<div class="links" id="links"></div>
|
||||
<div class="top-actions">
|
||||
<button type="button" id="themeToggle" title="切换主题">深色</button>
|
||||
<button type="button" class="primary" id="pickFiles">选择文件</button>
|
||||
<button type="button" id="pickFolder">选择文件夹</button>
|
||||
</div>
|
||||
|
||||
@@ -91,6 +91,7 @@ function AuthenticatedApp({
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onOpenOaDrive={user ? () => navigate('/space/oa/drive') : undefined}
|
||||
onOpenPage={user ? (pageId) => navigate(`/space/page/${pageId}`) : undefined}
|
||||
onOpenAdmin={
|
||||
user?.role === 'admin'
|
||||
@@ -121,6 +122,7 @@ function AuthenticatedApp({
|
||||
element={<FeedbackRoutes user={user} onLogout={handleLogout} />}
|
||||
/>
|
||||
<Route path="/health" element={<Navigate to="/" replace />} />
|
||||
<Route path="/oa-drive" element={<Navigate to="/space/oa/drive" replace />} />
|
||||
<Route path="/" element={chatElement} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
|
||||
@@ -18,6 +18,7 @@ import { WechatBindPrompt } from './WechatBindPrompt';
|
||||
import { WechatAccountButton } from './WechatAccountButton';
|
||||
import { ChatHeaderMoreMenu } from './ChatHeaderMoreMenu';
|
||||
import { NotificationCenter } from './NotificationCenter';
|
||||
import { ThemeToggle } from './ThemeToggle';
|
||||
import { GoalRunAwaitingBanner } from './GoalRunAwaitingBanner';
|
||||
import { HealthChannelBanner } from './HealthChannelBanner';
|
||||
import { HealthAlertsPanel } from './HealthAlertsPanel';
|
||||
@@ -173,6 +174,7 @@ export function ChatView({
|
||||
onGrantedSkillsUpdate,
|
||||
onLogout,
|
||||
onOpenSpace,
|
||||
onOpenOaDrive,
|
||||
onOpenPage,
|
||||
onOpenAdmin,
|
||||
onOpenFeedback,
|
||||
@@ -184,6 +186,7 @@ export function ChatView({
|
||||
onGrantedSkillsUpdate?: (skills: string[]) => void;
|
||||
onLogout?: () => void;
|
||||
onOpenSpace?: (target?: { categoryCode?: MindSpaceSaveCategory; pageId?: string }) => void;
|
||||
onOpenOaDrive?: () => void;
|
||||
onOpenPage?: (pageId: string) => void;
|
||||
onOpenAdmin?: () => void;
|
||||
onOpenFeedback?: () => void;
|
||||
@@ -327,6 +330,9 @@ export function ChatView({
|
||||
: pendingSessionTitle;
|
||||
|
||||
const moreMenuItems = [
|
||||
...(onOpenOaDrive
|
||||
? [{ id: 'oa-drive', label: 'OA 网盘', onClick: () => onOpenOaDrive() }]
|
||||
: []),
|
||||
...(onOpenAdmin
|
||||
? [{ id: 'admin', label: '管理', onClick: () => onOpenAdmin() }]
|
||||
: []),
|
||||
@@ -427,6 +433,16 @@ export function ChatView({
|
||||
管理
|
||||
</button>
|
||||
)}
|
||||
{onOpenOaDrive && (
|
||||
<button
|
||||
type="button"
|
||||
className="ghost-btn"
|
||||
data-analytics-action="open_oa_drive"
|
||||
onClick={() => onOpenOaDrive()}
|
||||
>
|
||||
OA 网盘
|
||||
</button>
|
||||
)}
|
||||
{onOpenSpace && (
|
||||
<button
|
||||
ref={spaceButtonRef}
|
||||
@@ -438,6 +454,7 @@ export function ChatView({
|
||||
我的空间
|
||||
</button>
|
||||
)}
|
||||
<ThemeToggle />
|
||||
{user && <WechatAccountButton returnTo={window.location.pathname} />}
|
||||
{onLogout && (
|
||||
<button type="button" className="ghost-btn logout-btn" data-analytics-action="logout" onClick={onLogout}>
|
||||
@@ -501,6 +518,7 @@ export function ChatView({
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
<ThemeToggle className="ghost-btn theme-toggle-btn theme-toggle-btn-mobile" />
|
||||
<ChatHeaderMoreMenu
|
||||
items={moreMenuItems}
|
||||
returnTo={window.location.pathname}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getStoredTheme, toggleTheme, type MemindTheme } from '../utils/theme';
|
||||
|
||||
export function ThemeToggle({
|
||||
className = 'ghost-btn theme-toggle-btn',
|
||||
}: {
|
||||
className?: string;
|
||||
}) {
|
||||
const [theme, setTheme] = useState<MemindTheme>(() => getStoredTheme());
|
||||
|
||||
useEffect(() => {
|
||||
const onChange = (event: Event) => {
|
||||
const detail = (event as CustomEvent<{ theme: MemindTheme }>).detail;
|
||||
if (detail?.theme) setTheme(detail.theme);
|
||||
};
|
||||
window.addEventListener('memind-theme-change', onChange);
|
||||
return () => window.removeEventListener('memind-theme-change', onChange);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={className}
|
||||
data-analytics-action="toggle_theme"
|
||||
aria-label={theme === 'light' ? '切换深色模式' : '切换浅色模式'}
|
||||
title={theme === 'light' ? '切换深色模式' : '切换浅色模式'}
|
||||
onClick={() => setTheme(toggleTheme())}
|
||||
>
|
||||
{theme === 'light' ? '深色' : '浅色'}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
+167
-31
@@ -1,7 +1,54 @@
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
:root,
|
||||
[data-theme='light'] {
|
||||
color-scheme: light;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
|
||||
--color-bg-base: #f4f1ea;
|
||||
--color-bg-surface: #fffaf0;
|
||||
--color-bg-elevated: #ffffff;
|
||||
--color-bg-active: #e8f0ea;
|
||||
--color-bg-code: #f0ebe0;
|
||||
--color-bg-warning: #fff6e5;
|
||||
--color-bg-error: #fff0f0;
|
||||
--color-bg-info: #eef6ff;
|
||||
|
||||
--color-text-primary: #18211d;
|
||||
--color-text-secondary: #3d4a44;
|
||||
--color-text-muted: #52605a;
|
||||
--color-text-faint: #6b7872;
|
||||
--color-text-meta: #7a8780;
|
||||
--color-text-link: #2f6f57;
|
||||
--color-text-error: #b42318;
|
||||
--color-text-info: #2458a6;
|
||||
|
||||
--color-border: rgba(24, 33, 29, 0.12);
|
||||
--color-border-input: rgba(24, 33, 29, 0.18);
|
||||
--color-border-warning: rgba(238, 176, 78, 0.45);
|
||||
--color-border-input-focus: #2f6f57;
|
||||
|
||||
--color-accent: #2f6f57;
|
||||
--color-accent-user: #2458a6;
|
||||
--color-danger: #c4453d;
|
||||
|
||||
--radius-sm: 8px;
|
||||
--radius-xs: 4px;
|
||||
--radius-md: 10px;
|
||||
--radius-lg: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-pill: 999px;
|
||||
|
||||
--space-xs: 4px;
|
||||
--space-sm: 8px;
|
||||
--space-md: 12px;
|
||||
--space-lg: 16px;
|
||||
|
||||
background: var(--color-bg-base);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
color-scheme: dark;
|
||||
|
||||
--color-bg-base: #0f1419;
|
||||
--color-bg-surface: #121820;
|
||||
--color-bg-elevated: #1c2735;
|
||||
@@ -28,21 +75,6 @@
|
||||
--color-accent: #3d8bfd;
|
||||
--color-accent-user: #2458a6;
|
||||
--color-danger: #c4453d;
|
||||
|
||||
--radius-sm: 8px;
|
||||
--radius-xs: 4px;
|
||||
--radius-md: 10px;
|
||||
--radius-lg: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-pill: 999px;
|
||||
|
||||
--space-xs: 4px;
|
||||
--space-sm: 8px;
|
||||
--space-md: 12px;
|
||||
--space-lg: 16px;
|
||||
|
||||
background: var(--color-bg-base);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -76,11 +108,9 @@ body,
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background:
|
||||
radial-gradient(circle at 50% 22%, rgba(86, 128, 255, 0.12), transparent 18%),
|
||||
radial-gradient(circle at 50% 56%, rgba(107, 138, 209, 0.08), transparent 34%),
|
||||
radial-gradient(circle at 12% 92%, rgba(67, 107, 180, 0.08), transparent 28%),
|
||||
radial-gradient(circle at 88% 8%, rgba(160, 97, 255, 0.05), transparent 22%),
|
||||
linear-gradient(180deg, #08101d 0%, #0a1220 44%, #0c1320 100%);
|
||||
radial-gradient(circle at 50% 22%, rgba(47, 111, 87, 0.08), transparent 18%),
|
||||
radial-gradient(circle at 50% 56%, rgba(238, 176, 78, 0.06), transparent 34%),
|
||||
linear-gradient(180deg, #fffaf0 0%, #f4f1ea 44%, #ece6d9 100%);
|
||||
}
|
||||
|
||||
.app-home::before {
|
||||
@@ -88,6 +118,21 @@ body,
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background:
|
||||
linear-gradient(90deg, rgba(24, 33, 29, 0.02), transparent 18%, transparent 82%, rgba(24, 33, 29, 0.02)),
|
||||
radial-gradient(circle at center, transparent 0%, rgba(244, 241, 234, 0.18) 72%, rgba(244, 241, 234, 0.42) 100%);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .app-home {
|
||||
background:
|
||||
radial-gradient(circle at 50% 22%, rgba(86, 128, 255, 0.12), transparent 18%),
|
||||
radial-gradient(circle at 50% 56%, rgba(107, 138, 209, 0.08), transparent 34%),
|
||||
radial-gradient(circle at 12% 92%, rgba(67, 107, 180, 0.08), transparent 28%),
|
||||
radial-gradient(circle at 88% 8%, rgba(160, 97, 255, 0.05), transparent 22%),
|
||||
linear-gradient(180deg, #08101d 0%, #0a1220 44%, #0c1320 100%);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .app-home::before {
|
||||
background:
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.015), transparent 18%, transparent 82%, rgba(255, 255, 255, 0.015)),
|
||||
radial-gradient(circle at center, transparent 0%, rgba(2, 8, 18, 0.18) 72%, rgba(2, 8, 18, 0.42) 100%);
|
||||
@@ -897,16 +942,32 @@ body,
|
||||
.header-home .ghost-btn,
|
||||
.header-home .header-icon-btn,
|
||||
.header-home .header-menu-btn {
|
||||
background: rgba(24, 33, 29, 0.04);
|
||||
color: rgba(24, 33, 29, 0.82);
|
||||
border: 1px solid rgba(24, 33, 29, 0.12);
|
||||
}
|
||||
|
||||
.header-home .header-title {
|
||||
color: rgba(24, 33, 29, 0.96);
|
||||
}
|
||||
|
||||
.header-home .header-sub {
|
||||
color: rgba(24, 33, 29, 0.62);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .header-home .ghost-btn,
|
||||
[data-theme='dark'] .header-home .header-icon-btn,
|
||||
[data-theme='dark'] .header-home .header-menu-btn {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: rgba(220, 231, 248, 0.82);
|
||||
border: 1px solid rgba(103, 123, 156, 0.22);
|
||||
}
|
||||
|
||||
.header-home .header-title {
|
||||
[data-theme='dark'] .header-home .header-title {
|
||||
color: rgba(238, 243, 255, 0.96);
|
||||
}
|
||||
|
||||
.header-home .header-sub {
|
||||
[data-theme='dark'] .header-home .header-sub {
|
||||
color: rgba(180, 193, 219, 0.74);
|
||||
}
|
||||
|
||||
@@ -3188,8 +3249,18 @@ body,
|
||||
}
|
||||
|
||||
.chat-input-shell-home {
|
||||
border: 1px solid rgba(96, 118, 153, 0.38);
|
||||
border: 1px solid rgba(24, 33, 29, 0.14);
|
||||
border-radius: 18px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(255, 250, 240, 0.96)),
|
||||
rgba(255, 255, 255, 0.94);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.6),
|
||||
0 16px 36px rgba(24, 33, 29, 0.08);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .chat-input-shell-home {
|
||||
border: 1px solid rgba(96, 118, 153, 0.38);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(18, 27, 42, 0.94), rgba(13, 20, 32, 0.96)),
|
||||
rgba(10, 18, 30, 0.94);
|
||||
@@ -3216,12 +3287,20 @@ body,
|
||||
padding-top: 14px;
|
||||
padding-bottom: 14px;
|
||||
font-size: 14px;
|
||||
color: #edf4ff;
|
||||
color: var(--color-text-primary);
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.chat-input-row-home textarea.input.chat-input-field::placeholder {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .chat-input-row-home textarea.input.chat-input-field {
|
||||
color: #edf4ff;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .chat-input-row-home textarea.input.chat-input-field::placeholder {
|
||||
color: rgba(175, 191, 216, 0.72);
|
||||
}
|
||||
|
||||
@@ -3367,10 +3446,18 @@ body,
|
||||
}
|
||||
|
||||
.chat-input-row-home .chat-input-task-status-text {
|
||||
color: rgba(175, 191, 216, 0.72);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.chat-input-row-home .chat-input-task-status-dots span {
|
||||
background: var(--color-text-muted);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .chat-input-row-home .chat-input-task-status-text {
|
||||
color: rgba(175, 191, 216, 0.72);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .chat-input-row-home .chat-input-task-status-dots span {
|
||||
background: rgba(175, 191, 216, 0.72);
|
||||
}
|
||||
|
||||
@@ -3766,6 +3853,13 @@ body,
|
||||
|
||||
.chat-input-row-home .chat-image-upload-trigger,
|
||||
.chat-input-row-home .voice-btn {
|
||||
border-color: var(--color-border-input);
|
||||
background: var(--color-bg-elevated);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .chat-input-row-home .chat-image-upload-trigger,
|
||||
[data-theme='dark'] .chat-input-row-home .voice-btn {
|
||||
border-color: rgba(110, 132, 168, 0.3);
|
||||
background: rgba(170, 192, 228, 0.08);
|
||||
color: #d7e4fb;
|
||||
@@ -12910,19 +13004,61 @@ body,
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100dvh;
|
||||
background: #f4f1ea;
|
||||
background: var(--color-bg-base);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.oa-drive-toolbar {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 10px 16px;
|
||||
border-bottom: 1px solid rgba(24, 33, 29, 0.08);
|
||||
background: #fffaf0;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
background: var(--color-bg-surface);
|
||||
}
|
||||
|
||||
.oa-drive-toolbar-start {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.oa-drive-toolbar-secondary {
|
||||
opacity: 0.88;
|
||||
}
|
||||
|
||||
.oa-drive-toolbar-end {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.oa-drive-toolbar-label {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.oa-drive-frame {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
background: #fff;
|
||||
background: var(--color-bg-base);
|
||||
}
|
||||
|
||||
.theme-toggle-btn {
|
||||
min-width: 44px;
|
||||
padding-inline: 12px;
|
||||
}
|
||||
|
||||
.theme-toggle-btn-mobile {
|
||||
min-width: 0;
|
||||
padding: 8px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { App } from './App';
|
||||
import { initTheme } from './utils/theme';
|
||||
import './index.css';
|
||||
|
||||
initTheme();
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<BrowserRouter>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { Navigate, useNavigate } from 'react-router-dom';
|
||||
import { ThemeToggle } from '../components/ThemeToggle';
|
||||
import type { PortalUser } from '../types';
|
||||
|
||||
function resolveUserLabel(user: PortalUser) {
|
||||
return user.displayName?.trim() || user.username?.trim() || user.slug?.trim() || '用户';
|
||||
}
|
||||
|
||||
export function OaDriveRoute({
|
||||
user,
|
||||
}: {
|
||||
@@ -15,9 +20,22 @@ export function OaDriveRoute({
|
||||
return (
|
||||
<div className="oa-drive-shell">
|
||||
<div className="oa-drive-toolbar">
|
||||
<button type="button" className="mindspace-inline-back" onClick={() => navigate('/space?category=oa')}>
|
||||
返回 OA 工作区
|
||||
</button>
|
||||
<div className="oa-drive-toolbar-start">
|
||||
<button type="button" className="mindspace-inline-back" onClick={() => navigate('/')}>
|
||||
返回聊天
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="mindspace-inline-back oa-drive-toolbar-secondary"
|
||||
onClick={() => navigate('/space?category=oa')}
|
||||
>
|
||||
OA 工作区
|
||||
</button>
|
||||
</div>
|
||||
<div className="oa-drive-toolbar-end">
|
||||
<span className="oa-drive-toolbar-label">{resolveUserLabel(user)} 的 OA 网盘</span>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</div>
|
||||
<iframe
|
||||
className="oa-drive-frame"
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
export type MemindTheme = 'light' | 'dark';
|
||||
|
||||
export const THEME_STORAGE_KEY = 'memind-theme';
|
||||
|
||||
export function getStoredTheme(): MemindTheme {
|
||||
const stored = localStorage.getItem(THEME_STORAGE_KEY);
|
||||
return stored === 'dark' ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
export function applyTheme(theme: MemindTheme) {
|
||||
const normalized: MemindTheme = theme === 'dark' ? 'dark' : 'light';
|
||||
document.documentElement.dataset.theme = normalized;
|
||||
document.documentElement.style.colorScheme = normalized;
|
||||
localStorage.setItem(THEME_STORAGE_KEY, normalized);
|
||||
window.dispatchEvent(new CustomEvent('memind-theme-change', { detail: { theme: normalized } }));
|
||||
}
|
||||
|
||||
export function initTheme() {
|
||||
applyTheme(getStoredTheme());
|
||||
}
|
||||
|
||||
export function toggleTheme(): MemindTheme {
|
||||
const next: MemindTheme = getStoredTheme() === 'light' ? 'dark' : 'light';
|
||||
applyTheme(next);
|
||||
return next;
|
||||
}
|
||||
Reference in New Issue
Block a user