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>
|
||||
|
||||
Reference in New Issue
Block a user