fix: Windows path handling, light theme default, file viewer in editor, first-user admin logic
Config: - Fixed SQLite path parsing on Windows (handle drive letters without prepending /) - Changed default theme from dark to light - Updated OAuth test credentials (gitea_oauth_client_id/secret) Auth: - First real user (excluding default admin with no password) becomes admin - Changed user count query to exclude users without password_hash File viewer: - Removed separate _render_file_viewer() —
This commit is contained in:
+8
-3
@@ -13,8 +13,8 @@ class Settings(BaseSettings):
|
|||||||
# Gitea
|
# Gitea
|
||||||
gitea_url: str = "https://git.dracodev.net"
|
gitea_url: str = "https://git.dracodev.net"
|
||||||
gitea_token: str = "change-me"
|
gitea_token: str = "change-me"
|
||||||
gitea_oauth_client_id: str = ""
|
gitea_oauth_client_id: str = "test-id"
|
||||||
gitea_oauth_client_secret: str = ""
|
gitea_oauth_client_secret: str = "test-secret"
|
||||||
gitea_webhook_secret: str = ""
|
gitea_webhook_secret: str = ""
|
||||||
|
|
||||||
# GitHub
|
# GitHub
|
||||||
@@ -53,7 +53,12 @@ class Settings(BaseSettings):
|
|||||||
if self.database_url == "sqlite:///:memory:":
|
if self.database_url == "sqlite:///:memory:":
|
||||||
return Path(":memory:") # Special SQLite in-memory
|
return Path(":memory:") # Special SQLite in-memory
|
||||||
if self.database_url.startswith("sqlite:///"):
|
if self.database_url.startswith("sqlite:///"):
|
||||||
return Path(self.database_url.replace("sqlite:///", "/"))
|
p = self.database_url.replace("sqlite:///", "", 1)
|
||||||
|
# On Windows, don't prepend / if path starts with a drive letter
|
||||||
|
import re
|
||||||
|
if re.match(r'^[a-zA-Z]:', p):
|
||||||
|
return Path(p)
|
||||||
|
return Path("/" + p)
|
||||||
return Path("/data/flowdeck.db")
|
return Path("/data/flowdeck.db")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -153,9 +153,11 @@ async def register(request: Request):
|
|||||||
if existing:
|
if existing:
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
return JSONResponse({"error": "Account already exists"}, status_code=409)
|
return JSONResponse({"error": "Account already exists"}, status_code=409)
|
||||||
# First user is admin
|
# First real user (excluding default admin with no password) is admin
|
||||||
user_count = conn.execute("SELECT COUNT(*) FROM users").fetchone()[0]
|
real_user_count = conn.execute(
|
||||||
is_admin = 1 if user_count == 0 else 0
|
"SELECT COUNT(*) FROM users WHERE password_hash IS NOT NULL AND password_hash != ''"
|
||||||
|
).fetchone()[0]
|
||||||
|
is_admin = 1 if real_user_count == 0 else 0
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO users (login, full_name, email, password_hash, is_admin) VALUES (?, ?, ?, ?, ?)",
|
"INSERT INTO users (login, full_name, email, password_hash, is_admin) VALUES (?, ?, ?, ?, ?)",
|
||||||
(email, name, email, hash_password(password), is_admin),
|
(email, name, email, hash_password(password), is_admin),
|
||||||
|
|||||||
+29
-5
@@ -926,10 +926,6 @@ async def view_page(request: Request, page_id: int):
|
|||||||
return HTMLResponse("<h2>Page not found</h2>", status_code=404)
|
return HTMLResponse("<h2>Page not found</h2>", status_code=404)
|
||||||
page = dict(row)
|
page = dict(row)
|
||||||
|
|
||||||
# ── File viewer for uploaded files ──
|
|
||||||
if page.get("content_format") == "file":
|
|
||||||
return _render_file_viewer(page, request)
|
|
||||||
|
|
||||||
ws = page.get("workspace", "")
|
ws = page.get("workspace", "")
|
||||||
parts = ws.split("/") if "/" in ws else ["", ""]
|
parts = ws.split("/") if "/" in ws else ["", ""]
|
||||||
owner, repo = parts[0], parts[1] if len(parts) > 1 else ""
|
owner, repo = parts[0], parts[1] if len(parts) > 1 else ""
|
||||||
@@ -941,10 +937,38 @@ async def view_page(request: Request, page_id: int):
|
|||||||
fav = conn.execute(
|
fav = conn.execute(
|
||||||
"SELECT id FROM favorites WHERE user_id=? AND page_id=?", (uid, page_id)
|
"SELECT id FROM favorites WHERE user_id=? AND page_id=?", (uid, page_id)
|
||||||
).fetchone()
|
).fetchone()
|
||||||
|
|
||||||
|
# Build page_data, including file metadata for uploaded files
|
||||||
|
page_data = {"id": page_id, "title": page.get("title"), "content_format": page.get("content_format", "blocks"), "content": page.get("content", ""), "favorited": fav is not None, "share_mode": page.get("share_mode", "private"), "published": bool(page.get("published", 0))}
|
||||||
|
|
||||||
|
# For file pages, extract file metadata and add to page_data
|
||||||
|
if page.get("content_format") == "file":
|
||||||
|
import json as _json
|
||||||
|
try:
|
||||||
|
meta = _json.loads(page.get("content", "{}"))
|
||||||
|
except _json.JSONDecodeError:
|
||||||
|
meta = {}
|
||||||
|
file_path = meta.get("file_path", "").replace("\\", "/")
|
||||||
|
mime_type = meta.get("mime_type", "application/octet-stream")
|
||||||
|
file_size = meta.get("size", 0)
|
||||||
|
# Build workspace_id from file_path
|
||||||
|
fp_parts = file_path.split("/")
|
||||||
|
ws_id = ""
|
||||||
|
for p in fp_parts:
|
||||||
|
if p.startswith("workspace_"):
|
||||||
|
ws_id = p.replace("workspace_", "")
|
||||||
|
break
|
||||||
|
filename = fp_parts[-1] if fp_parts else page.get("title", "File")
|
||||||
|
file_url = f"/api/files/{ws_id}/{filename}" if ws_id else ""
|
||||||
|
page_data["file_url"] = file_url
|
||||||
|
page_data["file_mime"] = mime_type
|
||||||
|
page_data["file_size"] = file_size
|
||||||
|
page_data["file_name"] = filename
|
||||||
|
|
||||||
ctx = {**sidebar, "page": page, "page_favorited": fav is not None,
|
ctx = {**sidebar, "page": page, "page_favorited": fav is not None,
|
||||||
"page_share_mode": page.get("share_mode", "private"),
|
"page_share_mode": page.get("share_mode", "private"),
|
||||||
"page_published": bool(page.get("published", 0)),
|
"page_published": bool(page.get("published", 0)),
|
||||||
"page_data": {"id": page_id, "title": page.get("title"), "content_format": page.get("content_format", "blocks"), "content": page.get("content", ""), "favorited": fav is not None, "share_mode": page.get("share_mode", "private"), "published": bool(page.get("published", 0))}}
|
"page_data": page_data}
|
||||||
template = env.get_template("page_editor.html")
|
template = env.get_template("page_editor.html")
|
||||||
response = template.render(**ctx)
|
response = template.render(**ctx)
|
||||||
return HTMLResponse(content=response, headers={"Cache-Control": "no-store, max-age=0"})
|
return HTMLResponse(content=response, headers={"Cache-Control": "no-store, max-age=0"})
|
||||||
|
|||||||
@@ -216,7 +216,6 @@ async def view_page_root(request: Request, page_id: int):
|
|||||||
"""Render a Markdown page at root level with workspace context — or file viewer."""
|
"""Render a Markdown page at root level with workspace context — or file viewer."""
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
from app.routers.board import _sidebar_data as board_sidebar
|
from app.routers.board import _sidebar_data as board_sidebar
|
||||||
from app.routers.board import _render_file_viewer
|
|
||||||
env = Environment(loader=FileSystemLoader("app/templates"))
|
env = Environment(loader=FileSystemLoader("app/templates"))
|
||||||
with get_conn() as conn:
|
with get_conn() as conn:
|
||||||
row = conn.execute("SELECT * FROM pages WHERE id=?", (page_id,)).fetchone()
|
row = conn.execute("SELECT * FROM pages WHERE id=?", (page_id,)).fetchone()
|
||||||
@@ -224,9 +223,6 @@ async def view_page_root(request: Request, page_id: int):
|
|||||||
return HTMLResponse("<h2>Page not found</h2>", status_code=404)
|
return HTMLResponse("<h2>Page not found</h2>", status_code=404)
|
||||||
page = dict(row)
|
page = dict(row)
|
||||||
|
|
||||||
# ── File viewer for uploaded files ──
|
|
||||||
if page.get("content_format") == "file":
|
|
||||||
return _render_file_viewer(page, request)
|
|
||||||
ws = page.get("workspace", "")
|
ws = page.get("workspace", "")
|
||||||
parts = ws.split("/") if "/" in ws else ["", ""]
|
parts = ws.split("/") if "/" in ws else ["", ""]
|
||||||
owner, repo = parts[0], parts[1] if len(parts) > 1 else ""
|
owner, repo = parts[0], parts[1] if len(parts) > 1 else ""
|
||||||
@@ -237,11 +233,42 @@ async def view_page_root(request: Request, page_id: int):
|
|||||||
"SELECT id, title FROM pages WHERE parent_id=? ORDER BY updated_at DESC",
|
"SELECT id, title FROM pages WHERE parent_id=? ORDER BY updated_at DESC",
|
||||||
(page_id,),
|
(page_id,),
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
fav = conn.execute(
|
||||||
|
"SELECT id FROM favorites WHERE user_id=? AND page_id=?",
|
||||||
|
(1, page_id),
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
# Build page_data, including file metadata for uploaded files
|
||||||
|
page_data = {"id": page_id, "title": page.get("title"), "content_format": page.get("content_format", "blocks"), "content": page.get("content", ""), "favorited": fav is not None, "share_mode": page.get("share_mode", "private"), "published": bool(page.get("published", 0))}
|
||||||
|
|
||||||
|
# For file pages, extract file metadata and add to page_data
|
||||||
|
if page.get("content_format") == "file":
|
||||||
|
import json as _json
|
||||||
|
try:
|
||||||
|
meta = _json.loads(page.get("content", "{}"))
|
||||||
|
except _json.JSONDecodeError:
|
||||||
|
meta = {}
|
||||||
|
file_path = meta.get("file_path", "").replace("\\", "/")
|
||||||
|
mime_type = meta.get("mime_type", "application/octet-stream")
|
||||||
|
file_size = meta.get("size", 0)
|
||||||
|
fp_parts = file_path.split("/")
|
||||||
|
ws_id = ""
|
||||||
|
for p in fp_parts:
|
||||||
|
if p.startswith("workspace_"):
|
||||||
|
ws_id = p.replace("workspace_", "")
|
||||||
|
break
|
||||||
|
filename = fp_parts[-1] if fp_parts else page.get("title", "File")
|
||||||
|
file_url = f"/api/files/{ws_id}/{filename}" if ws_id else ""
|
||||||
|
page_data["file_url"] = file_url
|
||||||
|
page_data["file_mime"] = mime_type
|
||||||
|
page_data["file_size"] = file_size
|
||||||
|
page_data["file_name"] = filename
|
||||||
|
|
||||||
ctx = {**sidebar, "page": page, "sub_pages": [dict(s) for s in subs],
|
ctx = {**sidebar, "page": page, "sub_pages": [dict(s) for s in subs],
|
||||||
"page_favorited": False,
|
"page_favorited": fav is not None,
|
||||||
"page_share_mode": page.get("share_mode", "private"),
|
"page_share_mode": page.get("share_mode", "private"),
|
||||||
"page_published": bool(page.get("published", 0)),
|
"page_published": bool(page.get("published", 0)),
|
||||||
"page_data": {"id": page_id, "title": page.get("title"), "content_format": page.get("content_format", "blocks"), "content": page.get("content", ""), "favorited": False, "share_mode": page.get("share_mode", "private"), "published": bool(page.get("published", 0))}}
|
"page_data": page_data}
|
||||||
template = env.get_template("page_editor.html")
|
template = env.get_template("page_editor.html")
|
||||||
response = template.render(**ctx)
|
response = template.render(**ctx)
|
||||||
return HTMLResponse(content=response, headers={"Cache-Control": "no-store, max-age=0"})
|
return HTMLResponse(content=response, headers={"Cache-Control": "no-store, max-age=0"})
|
||||||
@@ -866,8 +893,11 @@ async def serve_uploaded_file(ws_id: int, filename: str):
|
|||||||
"""Serve an uploaded file from disk."""
|
"""Serve an uploaded file from disk."""
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import mimetypes
|
import mimetypes
|
||||||
fp = Path(f"/data/uploads/workspace_{ws_id}/{filename}").resolve()
|
base_dir = Path(f"/data/uploads/workspace_{ws_id}").resolve()
|
||||||
if not str(fp).startswith(f"/data/uploads/workspace_{ws_id}/"):
|
fp = (base_dir / filename).resolve()
|
||||||
|
try:
|
||||||
|
fp.relative_to(base_dir)
|
||||||
|
except ValueError:
|
||||||
return JSONResponse({"error": "Path traversal denied"}, status_code=403)
|
return JSONResponse({"error": "Path traversal denied"}, status_code=403)
|
||||||
if not fp.exists():
|
if not fp.exists():
|
||||||
return JSONResponse({"error": "File not found"}, status_code=404)
|
return JSONResponse({"error": "File not found"}, status_code=404)
|
||||||
|
|||||||
+9
-51
@@ -1,12 +1,11 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr" data-theme="dark">
|
<html lang="fr" data-theme="light">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>FlowDeck — {% block title_prefix %}Home{% endblock %}</title>
|
<title>FlowDeck — {% block title_prefix %}Home{% endblock %}</title>
|
||||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||||
<link rel="stylesheet" href="/static/css/design-tokens.css?v=1.0">
|
<link rel="stylesheet" href="/static/css/app.css?v=3.0.0">
|
||||||
<link rel="stylesheet" href="/static/css/app.css?v=2.4.6">
|
|
||||||
<script src="/static/js/htmx.min.js"></script>
|
<script src="/static/js/htmx.min.js"></script>
|
||||||
<script src="/static/js/alpine.min.js" defer></script>
|
<script src="/static/js/alpine.min.js" defer></script>
|
||||||
<script src="/static/js/sortable.min.js" defer></script>
|
<script src="/static/js/sortable.min.js" defer></script>
|
||||||
@@ -1049,38 +1048,13 @@
|
|||||||
toggleTheme() {
|
toggleTheme() {
|
||||||
const html = document.documentElement;
|
const html = document.documentElement;
|
||||||
const current = html.getAttribute('data-theme');
|
const current = html.getAttribute('data-theme');
|
||||||
html.setAttribute('data-theme', current === 'dark' ? 'light' : 'dark');
|
const next = current === 'dark' ? 'light' : 'dark';
|
||||||
localStorage.setItem('fd_theme', current === 'dark' ? 'light' : 'dark');
|
html.setAttribute('data-theme', next);
|
||||||
if (current === 'dark') {
|
localStorage.setItem('fd_theme', next);
|
||||||
// Switching to light
|
|
||||||
html.style.setProperty('--bg-primary', '#FFFFFF');
|
|
||||||
html.style.setProperty('--bg-secondary', '#F7F6F3');
|
|
||||||
html.style.setProperty('--bg-tertiary', '#EDECE9');
|
|
||||||
html.style.setProperty('--text', '#37352F');
|
|
||||||
html.style.setProperty('--text-secondary', '#6B6966');
|
|
||||||
html.style.setProperty('--text-dim', '#9B9A97');
|
|
||||||
html.style.setProperty('--border', '#E0DFDC');
|
|
||||||
html.style.setProperty('--border-strong', '#CCCBC8');
|
|
||||||
} else {
|
|
||||||
// Switching to dark — remove overrides, use CSS defaults
|
|
||||||
['bg-primary','bg-secondary','bg-tertiary','text','text-secondary','text-dim','border','border-strong'].forEach(function(v) {
|
|
||||||
html.style.removeProperty('--'+v);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
initTheme() {
|
initTheme() {
|
||||||
var saved = localStorage.getItem('fd_theme') || 'dark';
|
var saved = localStorage.getItem('fd_theme') || 'light';
|
||||||
if (saved === 'light') {
|
document.documentElement.setAttribute('data-theme', saved);
|
||||||
document.documentElement.setAttribute('data-theme', 'light');
|
|
||||||
document.documentElement.style.setProperty('--bg-primary', '#FFFFFF');
|
|
||||||
document.documentElement.style.setProperty('--bg-secondary', '#F7F6F3');
|
|
||||||
document.documentElement.style.setProperty('--bg-tertiary', '#EDECE9');
|
|
||||||
document.documentElement.style.setProperty('--text', '#37352F');
|
|
||||||
document.documentElement.style.setProperty('--text-secondary', '#6B6966');
|
|
||||||
document.documentElement.style.setProperty('--text-dim', '#9B9A97');
|
|
||||||
document.documentElement.style.setProperty('--border', '#E0DFDC');
|
|
||||||
document.documentElement.style.setProperty('--border-strong', '#CCCBC8');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1115,24 +1089,8 @@
|
|||||||
|
|
||||||
// Apply saved theme on page load (AND on bfcache restore)
|
// Apply saved theme on page load (AND on bfcache restore)
|
||||||
function applySavedTheme() {
|
function applySavedTheme() {
|
||||||
var saved = localStorage.getItem('fd_theme') || 'dark';
|
var saved = localStorage.getItem('fd_theme') || 'light';
|
||||||
var h = document.documentElement;
|
document.documentElement.setAttribute('data-theme', saved);
|
||||||
h.setAttribute('data-theme', saved);
|
|
||||||
if (saved === 'light') {
|
|
||||||
h.style.setProperty('--bg-primary', '#FFFFFF');
|
|
||||||
h.style.setProperty('--bg-secondary', '#F7F6F3');
|
|
||||||
h.style.setProperty('--bg-tertiary', '#EDECE9');
|
|
||||||
h.style.setProperty('--text', '#37352F');
|
|
||||||
h.style.setProperty('--text-secondary', '#6B6966');
|
|
||||||
h.style.setProperty('--text-dim', '#9B9A97');
|
|
||||||
h.style.setProperty('--border', '#E0DFDC');
|
|
||||||
h.style.setProperty('--border-strong', '#CCCBC8');
|
|
||||||
} else {
|
|
||||||
// Remove light overrides, use CSS defaults
|
|
||||||
['bg-primary','bg-secondary','bg-tertiary','text','text-secondary','text-dim','border','border-strong'].forEach(function(v) {
|
|
||||||
h.style.removeProperty('--'+v);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
applySavedTheme();
|
applySavedTheme();
|
||||||
// Re-apply when page is restored from bfcache (e.g. browser back button)
|
// Re-apply when page is restored from bfcache (e.g. browser back button)
|
||||||
|
|||||||
@@ -758,7 +758,7 @@ document.addEventListener('alpine:init', () => {
|
|||||||
x-show="gwCtx.visible"
|
x-show="gwCtx.visible"
|
||||||
:style="'left:' + gwCtx.x + 'px; top:' + gwCtx.y + 'px'"
|
:style="'left:' + gwCtx.x + 'px; top:' + gwCtx.y + 'px'"
|
||||||
@click.outside="gwCtx.visible = false"
|
@click.outside="gwCtx.visible = false"
|
||||||
style="display:none;position:fixed;background:var(--bg-secondary);border:1px solid var(--border-strong);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.4);z-index:1100;min-width:160px;padding:4px;">
|
style="display:none;position:fixed;background:var(--bg-modal);border:1px solid var(--border);border-radius:var(--radius-md);box-shadow:var(--shadow-popover);z-index:1100;min-width:160px;padding:4px;">
|
||||||
<div class="ctx-item" @click="gwRename()">✏️ Rename</div>
|
<div class="ctx-item" @click="gwRename()">✏️ Rename</div>
|
||||||
<div class="ctx-item ctx-danger" @click="gwDelete()">🗑 Delete</div>
|
<div class="ctx-item ctx-danger" @click="gwDelete()">🗑 Delete</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
.source-filter-btn.active {
|
.source-filter-btn.active {
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
box-shadow: 0 1px 2px rgba(0,0,0,.2);
|
box-shadow: var(--shadow-popover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Card grid ── */
|
/* ── Card grid ── */
|
||||||
@@ -171,15 +171,15 @@
|
|||||||
}
|
}
|
||||||
.source-local {
|
.source-local {
|
||||||
background: rgba(100, 180, 100, 0.15);
|
background: rgba(100, 180, 100, 0.15);
|
||||||
color: #64b464;
|
color: var(--green);
|
||||||
}
|
}
|
||||||
.source-gitea {
|
.source-gitea {
|
||||||
background: rgba(100, 160, 220, 0.15);
|
background: rgba(100, 160, 220, 0.15);
|
||||||
color: #64a0dc;
|
color: var(--blue);
|
||||||
}
|
}
|
||||||
.source-github {
|
.source-github {
|
||||||
background: rgba(180, 140, 220, 0.15);
|
background: rgba(180, 140, 220, 0.15);
|
||||||
color: #b48cdc;
|
color: var(--purple);
|
||||||
}
|
}
|
||||||
.library-card-workspace {
|
.library-card-workspace {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
.modified-dot.show{display:block;}
|
.modified-dot.show{display:block;}
|
||||||
|
|
||||||
/* Offline banner */
|
/* Offline banner */
|
||||||
.offline-banner{position:fixed;top:0;left:0;right:0;background:#f85149;color:#fff;text-align:center;padding:6px 12px;font-size:12px;z-index:500;font-weight:500;}
|
.offline-banner{position:fixed;top:0;left:0;right:0;background:var(--danger);color:#fff;text-align:center;padding:6px 12px;font-size:12px;z-index:500;font-weight:500;}
|
||||||
|
|
||||||
/* Pinned indicator */
|
/* Pinned indicator */
|
||||||
.pin-icon{font-size:10px;color:var(--text-tertiary);cursor:pointer;margin-left:4px;opacity:0;transition:opacity 150ms;}
|
.pin-icon{font-size:10px;color:var(--text-tertiary);cursor:pointer;margin-left:4px;opacity:0;transition:opacity 150ms;}
|
||||||
@@ -83,6 +83,8 @@
|
|||||||
.bulk-bar{display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--accent);color:#fff;border-radius:8px;margin-bottom:10px;font-size:13px;font-weight:500;}
|
.bulk-bar{display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--accent);color:#fff;border-radius:8px;margin-bottom:10px;font-size:13px;font-weight:500;}
|
||||||
.bulk-bar .btn-danger{background:rgba(255,255,255,.15);color:#fff;border:1px solid rgba(255,255,255,.3);}
|
.bulk-bar .btn-danger{background:rgba(255,255,255,.15);color:#fff;border:1px solid rgba(255,255,255,.3);}
|
||||||
.bulk-bar .btn-secondary{background:rgba(255,255,255,.1);color:#fff;border:1px solid rgba(255,255,255,.2);}
|
.bulk-bar .btn-secondary{background:rgba(255,255,255,.1);color:#fff;border:1px solid rgba(255,255,255,.2);}
|
||||||
|
[data-theme="light"] .bulk-bar .btn-danger{background:rgba(0,0,0,.08);color:var(--text-primary);border:1px solid var(--border);}
|
||||||
|
[data-theme="light"] .bulk-bar .btn-secondary{background:rgba(0,0,0,.05);color:var(--text-primary);border:1px solid var(--border);}
|
||||||
|
|
||||||
/* Table view */
|
/* Table view */
|
||||||
.ws-table{width:100%;border-collapse:collapse;font-size:13px;}
|
.ws-table{width:100%;border-collapse:collapse;font-size:13px;}
|
||||||
@@ -94,8 +96,8 @@
|
|||||||
.ws-table .table-name:hover{color:var(--accent);}
|
.ws-table .table-name:hover{color:var(--accent);}
|
||||||
.ws-table .table-meta{color:var(--text-tertiary);font-size:12px;}
|
.ws-table .table-meta{color:var(--text-tertiary);font-size:12px;}
|
||||||
|
|
||||||
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;z-index:999;backdrop-filter:blur(4px);}
|
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.4);display:flex;align-items:center;justify-content:center;z-index:999;backdrop-filter:blur(4px);}
|
||||||
.modal-box{background:#1F1F1F;border:1px solid var(--border-strong);border-radius:16px;padding:28px;width:420px;max-width:90vw;box-shadow:0 20px 60px rgba(0,0,0,.5);}
|
.modal-box{background:var(--bg-modal);border:1px solid var(--border);border-radius:var(--radius-lg);padding:28px;width:420px;max-width:90vw;box-shadow:var(--shadow-modal);}
|
||||||
.modal-box h3{font-size:16px;margin-bottom:6px;display:flex;align-items:center;gap:8px;}
|
.modal-box h3{font-size:16px;margin-bottom:6px;display:flex;align-items:center;gap:8px;}
|
||||||
.modal-box .modal-sub{font-size:12px;color:var(--text-tertiary);margin-bottom:20px;}
|
.modal-box .modal-sub{font-size:12px;color:var(--text-tertiary);margin-bottom:20px;}
|
||||||
.modal-input{width:100%;padding:12px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:14px;outline:none;}
|
.modal-input{width:100%;padding:12px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:14px;outline:none;}
|
||||||
@@ -117,7 +119,7 @@
|
|||||||
.drop-overlay .drop-hint{background:var(--bg);border:2px dashed var(--accent);border-radius:16px;padding:40px 60px;text-align:center;color:var(--text);font-size:16px;}
|
.drop-overlay .drop-hint{background:var(--bg);border:2px dashed var(--accent);border-radius:16px;padding:40px 60px;text-align:center;color:var(--text);font-size:16px;}
|
||||||
.drop-overlay .drop-hint .icon{font-size:48px;display:block;margin-bottom:12px;}
|
.drop-overlay .drop-hint .icon{font-size:48px;display:block;margin-bottom:12px;}
|
||||||
.drop-target-highlight{outline:2px solid var(--accent)!important;outline-offset:-2px;border-radius:6px;background:rgba(35,131,226,.1)!important;}
|
.drop-target-highlight{outline:2px solid var(--accent)!important;outline-offset:-2px;border-radius:6px;background:rgba(35,131,226,.1)!important;}
|
||||||
.upload-progress{position:fixed;bottom:24px;right:24px;background:var(--bg);border:1px solid var(--border-strong);border-radius:12px;padding:16px 20px;z-index:200;box-shadow:0 8px 30px rgba(0,0,0,.4);min-width:200px;}
|
.upload-progress{position:fixed;bottom:24px;right:24px;background:var(--bg-modal);border:1px solid var(--border);border-radius:12px;padding:16px 20px;z-index:200;box-shadow:var(--shadow-popover);min-width:200px;}
|
||||||
.upload-progress .up-bar{height:4px;background:var(--bg-tertiary);border-radius:2px;margin-top:8px;overflow:hidden;}
|
.upload-progress .up-bar{height:4px;background:var(--bg-tertiary);border-radius:2px;margin-top:8px;overflow:hidden;}
|
||||||
.upload-progress .up-bar-fill{height:100%;background:var(--accent);border-radius:2px;transition:width 200ms;}
|
.upload-progress .up-bar-fill{height:100%;background:var(--accent);border-radius:2px;transition:width 200ms;}
|
||||||
|
|
||||||
@@ -153,7 +155,7 @@
|
|||||||
.sort-select:focus{border-color:var(--accent);}
|
.sort-select:focus{border-color:var(--accent);}
|
||||||
|
|
||||||
/* Undo toast */
|
/* Undo toast */
|
||||||
.undo-toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:var(--bg);border:1px solid var(--border-strong);border-radius:12px;padding:10px 20px;z-index:300;box-shadow:0 8px 30px rgba(0,0,0,.5);display:flex;align-items:center;gap:12px;font-size:13px;}
|
.undo-toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:var(--bg-modal);border:1px solid var(--border);border-radius:12px;padding:10px 20px;z-index:300;box-shadow:var(--shadow-popover);display:flex;align-items:center;gap:12px;font-size:13px;}
|
||||||
.undo-btn{background:var(--accent);color:#fff;border:none;padding:4px 12px;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600;}
|
.undo-btn{background:var(--accent);color:#fff;border:none;padding:4px 12px;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600;}
|
||||||
.undo-btn:hover{opacity:.85;}
|
.undo-btn:hover{opacity:.85;}
|
||||||
.undo-dismiss{background:none;border:none;color:var(--text-tertiary);cursor:pointer;font-size:18px;padding:0 4px;}
|
.undo-dismiss{background:none;border:none;color:var(--text-tertiary);cursor:pointer;font-size:18px;padding:0 4px;}
|
||||||
@@ -171,9 +173,9 @@
|
|||||||
.path-go:hover{opacity:.85;}
|
.path-go:hover{opacity:.85;}
|
||||||
|
|
||||||
/* File preview tooltip */
|
/* File preview tooltip */
|
||||||
.file-preview{position:fixed;z-index:300;background:#1a1a1a;background:var(--bg,#1a1a1a);border:1px solid var(--border-strong,#333);border-radius:10px;box-shadow:0 8px 30px rgba(0,0,0,.5);max-width:400px;min-width:200px;pointer-events:auto;}
|
.file-preview{position:fixed;z-index:300;background:var(--bg-modal);border:1px solid var(--border);border-radius:10px;box-shadow:var(--shadow-popover);max-width:400px;min-width:200px;pointer-events:auto;}
|
||||||
.file-preview-header{padding:8px 12px;font-size:12px;font-weight:600;color:var(--text-secondary,#888);border-bottom:1px solid var(--border,#333);}
|
.file-preview-header{padding:8px 12px;font-size:12px;font-weight:600;color:var(--text-secondary);border-bottom:1px solid var(--border);}
|
||||||
.file-preview-body{padding:10px 12px;margin:0;font-size:12px;line-height:1.5;color:var(--text,#ddd);max-height:200px;overflow:auto;font-family:'Fira Code','Cascadia Code',monospace;white-space:pre-wrap;word-wrap:break-word;background:#141414;}
|
.file-preview-body{padding:10px 12px;margin:0;font-size:12px;line-height:1.5;color:var(--text);max-height:200px;overflow:auto;font-family:'Fira Code','Cascadia Code',monospace;white-space:pre-wrap;word-wrap:break-word;background:var(--bg-secondary);}
|
||||||
|
|
||||||
/* Tag chips */
|
/* Tag chips */
|
||||||
.tag-chip{display:inline-flex;align-items:center;gap:2px;padding:1px 8px;border-radius:10px;font-size:11px;color:#fff;white-space:nowrap;margin:1px 2px;transition:all 150ms;}
|
.tag-chip{display:inline-flex;align-items:center;gap:2px;padding:1px 8px;border-radius:10px;font-size:11px;color:#fff;white-space:nowrap;margin:1px 2px;transition:all 150ms;}
|
||||||
@@ -254,13 +256,13 @@
|
|||||||
.view-dropdown-btn{display:flex;align-items:center;gap:6px;padding:5px 10px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:6px;cursor:pointer;font-size:12px;color:var(--text);white-space:nowrap;}
|
.view-dropdown-btn{display:flex;align-items:center;gap:6px;padding:5px 10px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:6px;cursor:pointer;font-size:12px;color:var(--text);white-space:nowrap;}
|
||||||
.view-dropdown-btn:hover{background:var(--bg-hover);border-color:var(--accent);}
|
.view-dropdown-btn:hover{background:var(--bg-hover);border-color:var(--accent);}
|
||||||
.dropdown-arrow{font-size:10px;color:var(--text-tertiary);}
|
.dropdown-arrow{font-size:10px;color:var(--text-tertiary);}
|
||||||
.view-dropdown-menu{position:absolute;top:100%;left:0;margin-top:4px;background:#1E1E1E;border:1px solid var(--border-strong);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.6);z-index:250;min-width:140px;overflow:hidden;}
|
.view-dropdown-menu{position:absolute;top:100%;left:0;margin-top:4px;background:var(--bg-modal);border:1px solid var(--border);border-radius:var(--radius-md);box-shadow:var(--shadow-popover);z-index:250;min-width:140px;overflow:hidden;}
|
||||||
.view-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 12px;background:none;border:none;color:var(--text);font-size:13px;cursor:pointer;text-align:left;}
|
.view-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 12px;background:none;border:none;color:var(--text);font-size:13px;cursor:pointer;text-align:left;}
|
||||||
.view-menu-item:hover{background:var(--bg-hover);}
|
.view-menu-item:hover{background:var(--bg-hover);}
|
||||||
.view-menu-item.active{background:rgba(35,131,226,.12);color:var(--accent);font-weight:500;}
|
.view-menu-item.active{background:rgba(35,131,226,.12);color:var(--accent);font-weight:500;}
|
||||||
|
|
||||||
/* Context menu */
|
/* Context menu */
|
||||||
.ctx-menu{position:fixed;background:#1E1E1E;border:1px solid var(--border-strong);border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,.5);min-width:200px;z-index:600;padding:4px 0;max-height:80vh;overflow-y:auto;}
|
.ctx-menu{position:fixed;background:var(--bg-modal);border:1px solid var(--border);border-radius:10px;box-shadow:var(--shadow-popover);min-width:200px;z-index:600;padding:4px 0;max-height:80vh;overflow-y:auto;}
|
||||||
.ctx-item{display:flex;align-items:center;gap:8px;padding:7px 14px;font-size:13px;color:var(--text);cursor:pointer;transition:background 100ms;}
|
.ctx-item{display:flex;align-items:center;gap:8px;padding:7px 14px;font-size:13px;color:var(--text);cursor:pointer;transition:background 100ms;}
|
||||||
.ctx-item:hover{background:var(--bg-hover);}
|
.ctx-item:hover{background:var(--bg-hover);}
|
||||||
.ctx-item.ctx-danger{color:var(--danger);}
|
.ctx-item.ctx-danger{color:var(--danger);}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{% extends "base.html" %} {% block page_icon %}📄{% endblock %} {% block
|
{% extends "base.html" %} {% block page_icon %}{% if page.content_format == 'file' %}📎{% else %}📄{% endif %}{% endblock %} {% block
|
||||||
page_title %}{{ page.title }}{% endblock %} {% block topbar %}
|
page_title %}{{ page.title }}{% endblock %} {% block topbar %}
|
||||||
{% set page_icon = "📄" %}
|
{% set page_icon = "📄" if page.content_format != 'file' else "📎" %}
|
||||||
{% set page_title = page.title %}
|
{% set page_title = page.title %}
|
||||||
{% set right_actions = '<span class="topbar-edited" style="cursor:pointer;" @click="window.E && (window.E.activityOpen = !window.E.activityOpen)">Edited <span x-text="window.E && window.E.timeAgo || \'\'"></span> ▾</span><button class="topbar-btn share-btn" @click="window.E && (window.E.shareOpen = !window.E.shareOpen)">🔒 Share ▾</button><button class="topbar-btn" @click="window.E && window.E.copyPageLink()" title="Copy link">🔗</button><button class="topbar-btn star-btn" @click="window.E && window.E.toggleFavorite()">⭐</button><button class="topbar-btn" @click="window.E && (window.E.moreOpen = !window.E.moreOpen)" style="position:relative">⋯</button>' %}
|
{% set right_actions = '<span class="topbar-edited" style="cursor:pointer;" @click="window.E && window.E.toggleActivityOpen()">Edited <span x-text="window.E && window.E.timeAgo || \'\'"></span> ▾</span><button class="topbar-btn share-btn" @click="window.E && window.E.toggleShareOpen()">🔒 Share ▾</button><button class="topbar-btn" @click="window.E && window.E.copyPageLink()" title="Copy link">🔗</button><button class="topbar-btn star-btn" @click="window.E && window.E.toggleFavorite()">⭐</button><button class="topbar-btn relative" @click="window.E && window.E.toggleMoreOpen()">⋯</button>' %}
|
||||||
{% include '_header.html' %}
|
{% include '_header.html' %}
|
||||||
{% endblock %} {% block content %}
|
{% endblock %} {% block content %}
|
||||||
<div class="page-editor-wrapper" x-data="editorState()">
|
<div class="page-editor-wrapper" x-data="editorState()">
|
||||||
@@ -12,7 +12,7 @@ page_title %}{{ page.title }}{% endblock %} {% block topbar %}
|
|||||||
<div
|
<div
|
||||||
class="share-dialog"
|
class="share-dialog"
|
||||||
x-show="shareOpen"
|
x-show="shareOpen"
|
||||||
@click.outside="shareOpen = false"
|
@click.outside="!_justOpened && (shareOpen = false)"
|
||||||
x-transition.opacity.scale.origin.top.right
|
x-transition.opacity.scale.origin.top.right
|
||||||
>
|
>
|
||||||
<!-- Header with tabs -->
|
<!-- Header with tabs -->
|
||||||
@@ -336,9 +336,9 @@ page_title %}{{ page.title }}{% endblock %} {% block topbar %}
|
|||||||
<!-- ═══════════ Activity Popover ═══════════ -->
|
<!-- ═══════════ Activity Popover ═══════════ -->
|
||||||
<div class="activity-popover"
|
<div class="activity-popover"
|
||||||
x-show="activityOpen"
|
x-show="activityOpen"
|
||||||
@click.outside="activityOpen = false"
|
@click.outside="!_justOpened && (activityOpen = false)"
|
||||||
x-transition.opacity.scale.origin.top.right
|
x-transition.opacity.scale.origin.top.right
|
||||||
style="position:absolute;top:100%;right:0;margin-top:4px;background:var(--bg-secondary);border:1px solid var(--border-strong);border-radius:12px;box-shadow:0 12px 32px rgba(0,0,0,.5);z-index:1000;width:280px;padding:12px;">
|
style="position:absolute;top:100%;right:0;margin-top:4px;background:var(--bg-modal);border:1px solid var(--border);border-radius:var(--radius-lg);box-shadow:var(--shadow-modal);z-index:1000;width:280px;padding:12px;">
|
||||||
<div style="font-size:12px;color:var(--text-secondary);margin-bottom:4px;">Edited <span x-text="timeAgo"></span></div>
|
<div style="font-size:12px;color:var(--text-secondary);margin-bottom:4px;">Edited <span x-text="timeAgo"></span></div>
|
||||||
<template x-if="pageCreated">
|
<template x-if="pageCreated">
|
||||||
<div style="font-size:12px;color:var(--text-tertiary);">Created <span x-text="formatDate(pageCreated)"></span></div>
|
<div style="font-size:12px;color:var(--text-tertiary);">Created <span x-text="formatDate(pageCreated)"></span></div>
|
||||||
@@ -349,7 +349,7 @@ page_title %}{{ page.title }}{% endblock %} {% block topbar %}
|
|||||||
<div
|
<div
|
||||||
class="more-menu"
|
class="more-menu"
|
||||||
x-show="moreOpen"
|
x-show="moreOpen"
|
||||||
@click.outside="moreOpen = false"
|
@click.outside="!_justOpened && (moreOpen = false)"
|
||||||
x-transition
|
x-transition
|
||||||
>
|
>
|
||||||
<div class="more-menu-item" @click="exportPage">↗ Export</div>
|
<div class="more-menu-item" @click="exportPage">↗ Export</div>
|
||||||
@@ -368,7 +368,7 @@ page_title %}{{ page.title }}{% endblock %} {% block topbar %}
|
|||||||
x-show="moveOpen"
|
x-show="moveOpen"
|
||||||
@click.outside="moveOpen = false"
|
@click.outside="moveOpen = false"
|
||||||
x-transition.opacity.scale
|
x-transition.opacity.scale
|
||||||
style="position:absolute;top:100%;right:0;margin-top:4px;background:var(--bg-secondary);border:1px solid var(--border-strong);border-radius:12px;box-shadow:0 12px 32px rgba(0,0,0,.5);z-index:1000;width:320px;padding:16px;">
|
style="position:absolute;top:100%;right:0;margin-top:4px;background:var(--bg-modal);border:1px solid var(--border);border-radius:var(--radius-lg);box-shadow:var(--shadow-modal);z-index:1000;width:320px;padding:16px;">
|
||||||
<h3 style="font-size:14px;margin-bottom:12px;">Move to workspace</h3>
|
<h3 style="font-size:14px;margin-bottom:12px;">Move to workspace</h3>
|
||||||
<div x-show="moveLoading" style="color:var(--text-tertiary);text-align:center;padding:20px;">Loading...</div>
|
<div x-show="moveLoading" style="color:var(--text-tertiary);text-align:center;padding:20px;">Loading...</div>
|
||||||
<div x-show="!moveLoading" style="max-height:240px;overflow-y:auto;">
|
<div x-show="!moveLoading" style="max-height:240px;overflow-y:auto;">
|
||||||
@@ -390,7 +390,7 @@ page_title %}{{ page.title }}{% endblock %} {% block topbar %}
|
|||||||
<!-- ═══════════ Page Content ═══════════ -->
|
<!-- ═══════════ Page Content ═══════════ -->
|
||||||
<div class="page-cover-area">
|
<div class="page-cover-area">
|
||||||
<div class="page-title-block">
|
<div class="page-title-block">
|
||||||
<span class="page-icon-emoji">📄</span>
|
<span class="page-icon-emoji">{% if page.content_format == 'file' %}📎{% else %}📄{% endif %}</span>
|
||||||
<div
|
<div
|
||||||
class="page-title-input"
|
class="page-title-input"
|
||||||
contenteditable="true"
|
contenteditable="true"
|
||||||
@@ -523,6 +523,7 @@ page_title %}{{ page.title }}{% endblock %} {% block topbar %}
|
|||||||
{{ page_data | tojson }}
|
{{ page_data | tojson }}
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
|
/* eslint-disable */
|
||||||
const CMDS=[{name:'BASIC',items:[
|
const CMDS=[{name:'BASIC',items:[
|
||||||
{id:'paragraph',name:'Text',icon:'¶'},{id:'heading_1',name:'Heading 1',icon:'H1'},{id:'heading_2',name:'Heading 2',icon:'H2'},
|
{id:'paragraph',name:'Text',icon:'¶'},{id:'heading_1',name:'Heading 1',icon:'H1'},{id:'heading_2',name:'Heading 2',icon:'H2'},
|
||||||
{id:'heading_3',name:'Heading 3',icon:'H3'},{id:'heading_4',name:'Heading 4',icon:'H4'},{id:'bulleted_list',name:'Bulleted list',icon:'•'},{id:'numbered_list',name:'Numbered list',icon:'1.'},
|
{id:'heading_3',name:'Heading 3',icon:'H3'},{id:'heading_4',name:'Heading 4',icon:'H4'},{id:'bulleted_list',name:'Bulleted list',icon:'•'},{id:'numbered_list',name:'Numbered list',icon:'1.'},
|
||||||
@@ -572,6 +573,14 @@ page_title %}{{ page.title }}{% endblock %} {% block topbar %}
|
|||||||
|
|
||||||
function renderBlock(b,idx){
|
function renderBlock(b,idx){
|
||||||
if(b.type==='divider')return `<div class="block-wrapper"><hr class="block-divider"></div>`;
|
if(b.type==='divider')return `<div class="block-wrapper"><hr class="block-divider"></div>`;
|
||||||
|
if(b.type==='image')return `<div class="block-wrapper"><div class="block-content block-image" style="text-align:center;padding:12px 0;"><img src="${b.src||''}" alt="${esc(b.alt||'')}" style="max-width:100%;max-height:70vh;border-radius:8px;display:block;margin:0 auto;" onerror="this.style.display='none';this.parentElement.innerHTML='<div style=\\'padding:40px;color:var(--text-tertiary)\\'>Image not found</div>'"></div></div>`;
|
||||||
|
if(b.type==='embed'){
|
||||||
|
if(b.embed_type==='pdf')return `<div class="block-wrapper"><div class="block-content block-embed" style="padding:0;"><iframe src="${b.src||''}" style="width:100%;height:80vh;border:none;border-radius:8px;"></iframe></div></div>`;
|
||||||
|
if(b.embed_type==='download'){
|
||||||
|
var sz=b.file_size||0;var szStr=sz<1024?sz+' B':sz<1048576?(sz/1024).toFixed(1)+' KB':(sz/1048576).toFixed(1)+' MB';
|
||||||
|
return `<div class="block-wrapper"><div class="block-content block-embed" style="text-align:center;padding:60px 20px;"><div style="font-size:48px;margin-bottom:12px;">📎</div><div style="font-size:16px;font-weight:500;color:var(--text-primary);margin-bottom:4px;">${esc(b.file_name||'File')}</div><div style="font-size:12px;color:var(--text-tertiary);margin-bottom:16px;">${szStr} · ${esc(b.file_mime||'')}</div><a href="${b.src||''}" download style="display:inline-block;padding:8px 20px;background:var(--accent);color:#fff;text-decoration:none;border-radius:8px;font-size:13px;font-weight:500;">⬇ Download</a></div></div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
const ph=b.type==='heading_1'?'Heading 1':b.type==='heading_2'?'Heading 2':b.type==='heading_3'?'Heading 3':b.type==='heading_4'?'Heading 4':b.type==='bulleted_list'?'List':b.type==='numbered_list'?'List':b.type==='to_do'?'To-do':b.type==='toggle'?'Toggle list':b.type==='quote'?'Empty quote':b.type==='code'?'Type code...':b.type==='callout'?'Type something...':'Press \'/\' for commands...';
|
const ph=b.type==='heading_1'?'Heading 1':b.type==='heading_2'?'Heading 2':b.type==='heading_3'?'Heading 3':b.type==='heading_4'?'Heading 4':b.type==='bulleted_list'?'List':b.type==='numbered_list'?'List':b.type==='to_do'?'To-do':b.type==='toggle'?'Toggle list':b.type==='quote'?'Empty quote':b.type==='code'?'Type code...':b.type==='callout'?'Type something...':'Press \'/\' for commands...';
|
||||||
let inner='';
|
let inner='';
|
||||||
if(b.type==='to_do')inner=`<input type="checkbox" class="todo-checkbox" ${b.checked?'checked':''} onchange="E._doToggle('${b.id}',this.checked)"><div data-bid="${b.id}" contenteditable="true" data-placeholder="${ph}" spellcheck="false">${esc(b.content)}</div>`;
|
if(b.type==='to_do')inner=`<input type="checkbox" class="todo-checkbox" ${b.checked?'checked':''} onchange="E._doToggle('${b.id}',this.checked)"><div data-bid="${b.id}" contenteditable="true" data-placeholder="${ph}" spellcheck="false">${esc(b.content)}</div>`;
|
||||||
@@ -623,7 +632,8 @@ page_title %}{{ page.title }}{% endblock %} {% block topbar %}
|
|||||||
_bid=Math.floor(Math.random()*10000);
|
_bid=Math.floor(Math.random()*10000);
|
||||||
const dataEl=document.getElementById('page-data');
|
const dataEl=document.getElementById('page-data');
|
||||||
if(dataEl){try{const data=JSON.parse(dataEl.textContent);this.pid=data.id;this.pageTitle=data.title||'';this.favorited=data.favorited||false;const fmt=data.content_format||'blocks';const raw=data.content||'';
|
if(dataEl){try{const data=JSON.parse(dataEl.textContent);this.pid=data.id;this.pageTitle=data.title||'';this.favorited=data.favorited||false;const fmt=data.content_format||'blocks';const raw=data.content||'';
|
||||||
if(fmt==='blocks'&&raw){try{this.blocks=JSON.parse(raw);this.blocks.forEach(b=>{if(!b.id)b.id=genId()});}catch(e){this.blocks=[];}}
|
if(fmt==='file'){this.fileData=data;this.loadFileContent(data);}
|
||||||
|
else if(fmt==='blocks'&&raw){try{this.blocks=JSON.parse(raw);this.blocks.forEach(b=>{if(!b.id)b.id=genId()});}catch(e){this.blocks=[];}}
|
||||||
else if(raw&&raw.trim())this.blocks=this.md2b(raw);}catch(e){}}
|
else if(raw&&raw.trim())this.blocks=this.md2b(raw);}catch(e){}}
|
||||||
if(!this.blocks.length)this.blocks=[this.mkB('paragraph','')];
|
if(!this.blocks.length)this.blocks=[this.mkB('paragraph','')];
|
||||||
window.E=this;SM.init();this.render();
|
window.E=this;SM.init();this.render();
|
||||||
@@ -633,13 +643,36 @@ page_title %}{{ page.title }}{% endblock %} {% block topbar %}
|
|||||||
if(typeof Sortable!=='undefined')Sortable.create(ct,{draggable:'.block-wrapper',handle:'.block-handle',animation:150,ghostClass:'sortable-ghost',dragClass:'sortable-drag',onEnd:evt=>{if(evt.oldIndex===evt.newIndex)return;const[item]=this.blocks.splice(evt.oldIndex,1);this.blocks.splice(evt.newIndex,0,item);this.dirty=true;this.autoSave();this.render();}});
|
if(typeof Sortable!=='undefined')Sortable.create(ct,{draggable:'.block-wrapper',handle:'.block-handle',animation:150,ghostClass:'sortable-ghost',dragClass:'sortable-drag',onEnd:evt=>{if(evt.oldIndex===evt.newIndex)return;const[item]=this.blocks.splice(evt.oldIndex,1);this.blocks.splice(evt.newIndex,0,item);this.dirty=true;this.autoSave();this.render();}});
|
||||||
},100);
|
},100);
|
||||||
},
|
},
|
||||||
|
fileData:null,
|
||||||
|
async loadFileContent(data){
|
||||||
|
const url=data.file_url||'';const mime=data.file_mime||'';const name=data.file_name||'';const self=this;
|
||||||
|
if(!url){this.blocks=[this.mkB('paragraph','File not available')];this.render();return;}
|
||||||
|
const ext=name.toLowerCase().split('.').pop();
|
||||||
|
if(mime.startsWith('image/')||['png','jpg','jpeg','gif','webp','svg','bmp','ico'].includes(ext)){
|
||||||
|
this.blocks=[this.mkB('image','',{src:url,alt:name})];this.render();return;
|
||||||
|
}
|
||||||
|
if(mime==='application/pdf'||ext==='pdf'){
|
||||||
|
this.blocks=[this.mkB('embed','',{src:url,embed_type:'pdf'})];this.render();return;
|
||||||
|
}
|
||||||
|
const codeExts=['py','js','ts','jsx','tsx','html','htm','css','json','xml','yaml','yml','md','markdown','sql','sh','bash','ps1','bat','cmd','rb','go','rs','java','c','cpp','h','hpp','php','swift','kt','scala','r','toml','ini','cfg','conf','env','txt','log'];
|
||||||
|
const langMap={'py':'python','js':'javascript','ts':'typescript','jsx':'javascript','tsx':'typescript','html':'html','htm':'html','css':'css','json':'json','xml':'xml','yaml':'yaml','yml':'yaml','md':'markdown','markdown':'markdown','sql':'sql','sh':'bash','bash':'bash','ps1':'powershell','bat':'plaintext','cmd':'plaintext','rb':'ruby','go':'go','rs':'rust','java':'java','c':'c','cpp':'cpp','h':'c','hpp':'cpp','php':'php','swift':'swift','kt':'kotlin','scala':'scala','r':'r','toml':'toml','ini':'ini','cfg':'ini','conf':'ini','env':'ini','txt':'plaintext','log':'plaintext'};
|
||||||
|
if(mime.startsWith('text/')||codeExts.includes(ext)){
|
||||||
|
try{const r=await fetch(url);const text=await r.text();
|
||||||
|
if(ext==='md'||ext==='markdown'){this.blocks=this.md2b(text);}
|
||||||
|
else{this.blocks=[this.mkB('code',text,{language:langMap[ext]||'Plain Text'})];}
|
||||||
|
this.render();
|
||||||
|
}catch(e){this.blocks=[this.mkB('paragraph','Error loading file: '+e.message)];this.render();}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.blocks=[this.mkB('embed','',{src:url,embed_type:'download',file_name:name,file_size:data.file_size||0,file_mime:mime})];this.render();
|
||||||
|
},
|
||||||
mkB(type,content,extras){const b={id:genId(),type,content:content||'',...(extras||{})};if(type==='toggle')b.expanded=true;if(type==='to_do')b.checked=false;return b;},
|
mkB(type,content,extras){const b={id:genId(),type,content:content||'',...(extras||{})};if(type==='toggle')b.expanded=true;if(type==='to_do')b.checked=false;return b;},
|
||||||
getEl(bid){const ct=document.getElementById('_blocksCt');return ct?ct.querySelector(`[data-bid="${bid}"]`):null;},
|
getEl(bid){const ct=document.getElementById('_blocksCt');return ct?ct.querySelector(`[data-bid="${bid}"]`):null;},
|
||||||
getIdx(bid){return this.blocks.findIndex(b=>b.id===bid);},
|
getIdx(bid){return this.blocks.findIndex(b=>b.id===bid);},
|
||||||
getActiveBlock(){const el=document.activeElement;if(!el||!el.hasAttribute('data-bid'))return null;return{el,bid:el.dataset.bid,idx:this.getIdx(el.dataset.bid)};},
|
getActiveBlock(){const el=document.activeElement;if(!el||!el.hasAttribute('data-bid'))return null;return{el,bid:el.dataset.bid,idx:this.getIdx(el.dataset.bid)};},
|
||||||
focusBlock(){const b=this.blocks[0];if(!b)return;const el=this.getEl(b.id);if(el){el.focus();ce(el);}},
|
focusBlock(){const b=this.blocks[0];if(!b)return;const el=this.getEl(b.id);if(el){el.focus();ce(el);}},
|
||||||
onCtClick(e){if(e.target===document.getElementById('_blocksCt')||e.target?.classList?.contains('blocks-empty')){if(!this.blocks.length)this.addAt(0);else{const b=this.blocks[this.blocks.length-1];const el=b&&this.getEl(b.id);if(el){el.focus();ce(el);}}}},
|
onCtClick(e){if(e.target===document.getElementById('_blocksCt')||e.target?.classList?.contains('blocks-empty')){if(!this.blocks.length)this.addAt(0);else{const b=this.blocks[this.blocks.length-1];const el=b&&this.getEl(b.id);if(el){el.focus();ce(el);}}}},
|
||||||
sync(){const ct=document.getElementById('_blocksCt');if(!ct)return;for(const b of this.blocks){if(b.type==='divider')continue;const el=ct.querySelector(`[data-bid="${b.id}"]`);if(el)b.content=el.textContent||'';}},
|
sync(){const ct=document.getElementById('_blocksCt');if(!ct)return;for(const b of this.blocks){if(b.type==='divider'||b.type==='image'||b.type==='embed')continue;const el=ct.querySelector(`[data-bid="${b.id}"]`);if(el)b.content=el.textContent||'';}},
|
||||||
render(){const ct=document.getElementById('_blocksCt');if(!ct)return;let html='';for(let i=0;i<this.blocks.length;i++)html+=renderBlock(this.blocks[i],i);ct.innerHTML=html;
|
render(){const ct=document.getElementById('_blocksCt');if(!ct)return;let html='';for(let i=0;i<this.blocks.length;i++)html+=renderBlock(this.blocks[i],i);ct.innerHTML=html;
|
||||||
ct.querySelectorAll('.block-wrapper').forEach(w=>{w.onmouseenter=()=>{w.querySelector('.block-handle')?.classList.add('visible');w.querySelector('.block-actions')?.classList.add('visible');};w.onmouseleave=()=>{w.querySelector('.block-handle')?.classList.remove('visible');w.querySelector('.block-actions')?.classList.remove('visible');};});
|
ct.querySelectorAll('.block-wrapper').forEach(w=>{w.onmouseenter=()=>{w.querySelector('.block-handle')?.classList.add('visible');w.querySelector('.block-actions')?.classList.add('visible');};w.onmouseleave=()=>{w.querySelector('.block-handle')?.classList.remove('visible');w.querySelector('.block-actions')?.classList.remove('visible');};});
|
||||||
ct.querySelectorAll('[data-placeholder]').forEach(el=>{el.innerHTML=el.innerHTML.replace(/<br[^>]*>/gi,'').trim()!==''?el.innerHTML:'';el.classList.toggle('empty',!el.textContent.trim());});
|
ct.querySelectorAll('[data-placeholder]').forEach(el=>{el.innerHTML=el.innerHTML.replace(/<br[^>]*>/gi,'').trim()!==''?el.innerHTML:'';el.classList.toggle('empty',!el.textContent.trim());});
|
||||||
@@ -653,14 +686,28 @@ page_title %}{{ page.title }}{% endblock %} {% block topbar %}
|
|||||||
const csrf=document.cookie.match(/csrf_token=([^;]+)/);
|
const csrf=document.cookie.match(/csrf_token=([^;]+)/);
|
||||||
const m=this.favorited?'DELETE':'POST';
|
const m=this.favorited?'DELETE':'POST';
|
||||||
fetch(`/board/api/favorites/${this.pid}`,{method:m,headers:{'X-CSRF-Token':csrf?csrf[1]:''}})
|
fetch(`/board/api/favorites/${this.pid}`,{method:m,headers:{'X-CSRF-Token':csrf?csrf[1]:''}})
|
||||||
.then(r=>r.json()).then(()=>{this.favorited=!this.favorited;}).catch(()=>{});
|
.then(r=>r.json()).then(()=>{this.favorited=!this.favorited;this.showToast(this.favorited?'Added to favorites':'Removed from favorites');}).catch(()=>{this.showToast('Failed to toggle favorite');});
|
||||||
},
|
},
|
||||||
|
toggleActivityOpen(){
|
||||||
|
if(!this.activityOpen){this.activityOpen=true;this._justOpened=true;setTimeout(()=>this._justOpened=false,200);}
|
||||||
|
else{this.activityOpen=false;}
|
||||||
|
},
|
||||||
|
toggleShareOpen(){
|
||||||
|
if(!this.shareOpen){this.shareOpen=true;this._justOpened=true;setTimeout(()=>this._justOpened=false,200);}
|
||||||
|
else{this.shareOpen=false;}
|
||||||
|
},
|
||||||
|
toggleMoreOpen(){
|
||||||
|
if(!this.moreOpen){this.moreOpen=true;this._justOpened=true;setTimeout(()=>this._justOpened=false,200);}
|
||||||
|
else{this.moreOpen=false;}
|
||||||
|
},
|
||||||
|
_justOpened:false,
|
||||||
togglePublish(){this.pagePublished=!this.pagePublished;this.saveShare();},
|
togglePublish(){this.pagePublished=!this.pagePublished;this.saveShare();},
|
||||||
saveShare(){
|
saveShare(){
|
||||||
const csrf=document.cookie.match(/csrf_token=([^;]+)/);
|
const csrf=document.cookie.match(/csrf_token=([^;]+)/);
|
||||||
fetch(`/board/api/share/${this.pid}`,{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':csrf?csrf[1]:''},body:JSON.stringify({mode:this.generalAccess,published:this.pagePublished})}).catch(()=>{});
|
fetch(`/board/api/share/${this.pid}`,{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':csrf?csrf[1]:''},body:JSON.stringify({mode:this.generalAccess,published:this.pagePublished})}).catch(()=>{});
|
||||||
},
|
},
|
||||||
copyPageLink(){
|
async copyPageLink(){
|
||||||
|
console.log('copyPageLink invoked');
|
||||||
const url = this.pageUrl;
|
const url = this.pageUrl;
|
||||||
const fallback = () => {
|
const fallback = () => {
|
||||||
const ta = document.createElement('textarea');
|
const ta = document.createElement('textarea');
|
||||||
@@ -669,9 +716,8 @@ page_title %}{{ page.title }}{% endblock %} {% block topbar %}
|
|||||||
document.execCommand('copy'); document.body.removeChild(ta);
|
document.execCommand('copy'); document.body.removeChild(ta);
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
navigator.clipboard.writeText(url).then(() => {
|
await navigator.clipboard.writeText(url);
|
||||||
this.showToast('Link copied to clipboard');
|
this.showToast('Link copied to clipboard');
|
||||||
}).catch(() => { fallback(); this.showToast('Link copied to clipboard'); });
|
|
||||||
} catch(e) { fallback(); this.showToast('Link copied to clipboard'); }
|
} catch(e) { fallback(); this.showToast('Link copied to clipboard'); }
|
||||||
},
|
},
|
||||||
showToast(msg){
|
showToast(msg){
|
||||||
@@ -839,8 +885,9 @@ page_title %}{{ page.title }}{% endblock %} {% block topbar %}
|
|||||||
fmtApply(f){this.fmt.open=false;if(!this._sel)return;const s=window.getSelection();s.removeAllRanges();s.addRange(this._sel.range);document.execCommand(f==='strikeThrough'?'strikeThrough':f);this._sel=null;this.dirty=true;this.autoSave();},
|
fmtApply(f){this.fmt.open=false;if(!this._sel)return;const s=window.getSelection();s.removeAllRanges();s.addRange(this._sel.range);document.execCommand(f==='strikeThrough'?'strikeThrough':f);this._sel=null;this.dirty=true;this.autoSave();},
|
||||||
fmtLink(){this.fmt.open=false;const u=prompt('URL:');if(u){document.execCommand('createLink',false,u);this.dirty=true;this.autoSave();}},
|
fmtLink(){this.fmt.open=false;const u=prompt('URL:');if(u){document.execCommand('createLink',false,u);this.dirty=true;this.autoSave();}},
|
||||||
pastePlain(e){e.preventDefault();document.execCommand('insertText',false,(e.clipboardData||window.clipboardData).getData('text/plain'));this.dirty=true;this.autoSave();},
|
pastePlain(e){e.preventDefault();document.execCommand('insertText',false,(e.clipboardData||window.clipboardData).getData('text/plain'));this.dirty=true;this.autoSave();},
|
||||||
autoSave(){clearTimeout(this.st);this.st=setTimeout(()=>this.save(),1500);},
|
autoSave(){if(this.fileData)return;clearTimeout(this.st);this.st=setTimeout(()=>this.save(),1500);},
|
||||||
save(){
|
save(){
|
||||||
|
if(this.fileData)return;
|
||||||
if(this.saving)return;
|
if(this.saving)return;
|
||||||
this.sync();
|
this.sync();
|
||||||
const t=document.getElementById('_titleEl');if(t)this.pageTitle=t.textContent?.trim()||'New page';
|
const t=document.getElementById('_titleEl');if(t)this.pageTitle=t.textContent?.trim()||'New page';
|
||||||
|
|||||||
+12
-32
@@ -11,8 +11,8 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<style>
|
<style>
|
||||||
.settings-overlay{position:fixed;inset:0;z-index:500;display:flex;background:rgba(0,0,0,.5);backdrop-filter:blur(2px);}
|
.settings-overlay{position:fixed;inset:0;z-index:500;display:flex;background:rgba(0,0,0,.4);backdrop-filter:blur(2px);}
|
||||||
.settings-panel{display:flex;width:1050px;max-width:98vw;height:85vh;margin:auto;background:var(--bg-primary);border:1px solid var(--border-strong);border-radius:14px;box-shadow:0 16px 48px rgba(0,0,0,.6);overflow:hidden;}
|
.settings-panel{display:flex;width:1050px;max-width:98vw;height:85vh;margin:auto;background:var(--bg-primary);border:1px solid var(--border-strong);border-radius:14px;box-shadow:var(--shadow-modal);overflow:hidden;}
|
||||||
.settings-nav{width:200px;min-width:200px;background:var(--bg-sidebar);border-right:1px solid var(--border);padding:8px 0;overflow-y:auto;}
|
.settings-nav{width:200px;min-width:200px;background:var(--bg-sidebar);border-right:1px solid var(--border);padding:8px 0;overflow-y:auto;}
|
||||||
.settings-nav-header{padding:12px 16px;font-size:13px;font-weight:600;color:var(--text-dim);text-transform:uppercase;letter-spacing:.5px;}
|
.settings-nav-header{padding:12px 16px;font-size:13px;font-weight:600;color:var(--text-dim);text-transform:uppercase;letter-spacing:.5px;}
|
||||||
.settings-nav-item{display:flex;align-items:center;gap:8px;padding:6px 16px;font-size:13px;color:var(--text-secondary);cursor:pointer;transition:background 100ms;border-radius:0;}
|
.settings-nav-item{display:flex;align-items:center;gap:8px;padding:6px 16px;font-size:13px;color:var(--text-secondary);cursor:pointer;transition:background 100ms;border-radius:0;}
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
.avatar-upload-input{display:none;}
|
.avatar-upload-input{display:none;}
|
||||||
|
|
||||||
/* Avatar color swatches */
|
/* Avatar color swatches */
|
||||||
.avatar-color-swatch{width:40px;height:40px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;color:#fff;cursor:pointer;border:2px solid transparent;transition:all 120ms;}
|
.avatar-color-swatch{width:40px;height:40px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;color:var(--text-primary);cursor:pointer;border:2px solid transparent;transition:all 120ms;}
|
||||||
.avatar-color-swatch:hover{transform:scale(1.1);}
|
.avatar-color-swatch:hover{transform:scale(1.1);}
|
||||||
.avatar-color-swatch.selected{border-color:var(--accent);box-shadow:0 0 0 2px rgba(35,131,226,.4);}
|
.avatar-color-swatch.selected{border-color:var(--accent);box-shadow:0 0 0 2px rgba(35,131,226,.4);}
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
.color-swatch.selected{border-color:var(--text);box-shadow:0 0 0 2px var(--accent);}
|
.color-swatch.selected{border-color:var(--text);box-shadow:0 0 0 2px var(--accent);}
|
||||||
|
|
||||||
/* Admin tables */
|
/* Admin tables */
|
||||||
.table-wrap{overflow-x:auto;border-radius:8px;border:1px solid #333;}
|
.table-wrap{overflow-x:auto;border-radius:8px;border:1px solid var(--border);}
|
||||||
.admin-table{width:100%;border-collapse:collapse;font-size:13px;}
|
.admin-table{width:100%;border-collapse:collapse;font-size:13px;}
|
||||||
.admin-table th{background:var(--bg-tertiary);color:var(--text-dim);font-weight:500;padding:8px 12px;text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:.5px;white-space:nowrap;}
|
.admin-table th{background:var(--bg-tertiary);color:var(--text-dim);font-weight:500;padding:8px 12px;text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:.5px;white-space:nowrap;}
|
||||||
.admin-table td{padding:8px 12px;border-top:1px solid var(--border);color:var(--text);}
|
.admin-table td{padding:8px 12px;border-top:1px solid var(--border);color:var(--text);}
|
||||||
@@ -63,11 +63,11 @@
|
|||||||
.stat-value{font-size:22px;font-weight:700;color:var(--accent);}
|
.stat-value{font-size:22px;font-weight:700;color:var(--accent);}
|
||||||
.stat-label{font-size:11px;color:var(--text-dim);margin-top:2px;text-transform:uppercase;letter-spacing:.5px;}
|
.stat-label{font-size:11px;color:var(--text-dim);margin-top:2px;text-transform:uppercase;letter-spacing:.5px;}
|
||||||
.role-badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:500;}
|
.role-badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:500;}
|
||||||
.role-admin{background:rgba(35,131,226,.2);color:#4DA3F5;}
|
.role-admin{background:rgba(35,131,226,.2);color:var(--accent);}
|
||||||
.role-user{background:rgba(120,119,116,.2);color:var(--text-dim);}
|
.role-user{background:rgba(120,119,116,.2);color:var(--text-dim);}
|
||||||
.status-dot{display:inline-block;width:6px;height:6px;border-radius:50%;margin-right:5px;vertical-align:middle;}
|
.status-dot{display:inline-block;width:6px;height:6px;border-radius:50%;margin-right:5px;vertical-align:middle;}
|
||||||
.status-dot.active{background:#0F7B6C;}
|
.status-dot.active{background:var(--toast-success-bg);}
|
||||||
.status-dot.inactive{background:#E03E3E;}
|
.status-dot.inactive{background:var(--danger);}
|
||||||
|
|
||||||
/* Close button */
|
/* Close button */
|
||||||
.settings-close{position:absolute;top:12px;right:12px;width:32px;height:32px;background:none;border:none;color:var(--text-dim);font-size:20px;cursor:pointer;border-radius:6px;display:flex;align-items:center;justify-content:center;}
|
.settings-close{position:absolute;top:12px;right:12px;width:32px;height:32px;background:none;border:none;color:var(--text-dim);font-size:20px;cursor:pointer;border-radius:6px;display:flex;align-items:center;justify-content:center;}
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
<span x-show="!accountSaving">💾 Save changes</span>
|
<span x-show="!accountSaving">💾 Save changes</span>
|
||||||
<span x-show="accountSaving">Saving…</span>
|
<span x-show="accountSaving">Saving…</span>
|
||||||
</button>
|
</button>
|
||||||
<span x-show="accountMsg" x-text="accountMsg" style="margin-left:12px;font-size:13px;color:#0F7B6C;"></span>
|
<span x-show="accountMsg" x-text="accountMsg" style="margin-left:12px;font-size:13px;color:var(--toast-success-bg);"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -280,7 +280,7 @@
|
|||||||
<h3 style="margin:0 0 8px;">Delete tag</h3>
|
<h3 style="margin:0 0 8px;">Delete tag</h3>
|
||||||
<p style="color:var(--text-dim);margin:0 0 16px;">
|
<p style="color:var(--text-dim);margin:0 0 16px;">
|
||||||
Are you sure you want to delete the tag "<strong x-text="deletingTag?.name"></strong>"?
|
Are you sure you want to delete the tag "<strong x-text="deletingTag?.name"></strong>"?
|
||||||
<span x-show="deletingTag?.count > 0" style="color:#E03E3E;display:block;margin-top:4px;">
|
<span x-show="deletingTag?.count > 0" style="color:var(--danger);display:block;margin-top:4px;">
|
||||||
⚠️ This tag is used on <span x-text="deletingTag?.count"></span> item(s).
|
⚠️ This tag is used on <span x-text="deletingTag?.count"></span> item(s).
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
@@ -324,7 +324,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-control">
|
<div class="setting-control">
|
||||||
<span x-show="giteaLinked" style="font-size:12px;color:#0F7B6C;">✅ Active</span>
|
<span x-show="giteaLinked" style="font-size:12px;color:var(--toast-success-bg);">✅ Active</span>
|
||||||
<button x-show="giteaLinked && authMethod!=='gitea'" class="btn-sm" style="color:var(--danger);" @click="disconnectGitea()">Disconnect</button>
|
<button x-show="giteaLinked && authMethod!=='gitea'" class="btn-sm" style="color:var(--danger);" @click="disconnectGitea()">Disconnect</button>
|
||||||
<a x-show="!giteaLinked" class="btn-sm" href="/auth/login?provider=gitea&mode=link" style="text-decoration:none;color:var(--accent);">🔗 Connect</a>
|
<a x-show="!giteaLinked" class="btn-sm" href="/auth/login?provider=gitea&mode=link" style="text-decoration:none;color:var(--accent);">🔗 Connect</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -339,7 +339,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-control">
|
<div class="setting-control">
|
||||||
<span x-show="githubLinked" style="font-size:12px;color:#0F7B6C;">✅ Active</span>
|
<span x-show="githubLinked" style="font-size:12px;color:var(--toast-success-bg);">✅ Active</span>
|
||||||
<button x-show="githubLinked && authMethod!=='github'" class="btn-sm" style="color:var(--danger);" @click="disconnectGithub()">Disconnect</button>
|
<button x-show="githubLinked && authMethod!=='github'" class="btn-sm" style="color:var(--danger);" @click="disconnectGithub()">Disconnect</button>
|
||||||
<a x-show="!githubLinked" class="btn-sm" href="/auth/login?provider=github&mode=link" style="text-decoration:none;color:var(--accent);">🔗 Connect</a>
|
<a x-show="!githubLinked" class="btn-sm" href="/auth/login?provider=github&mode=link" style="text-decoration:none;color:var(--accent);">🔗 Connect</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -522,7 +522,7 @@ document.addEventListener('alpine:init', function() {
|
|||||||
giteaLinked: false,
|
giteaLinked: false,
|
||||||
githubLinked: false,
|
githubLinked: false,
|
||||||
// Theme
|
// Theme
|
||||||
theme: localStorage.getItem('fd_theme') || 'dark',
|
theme: localStorage.getItem('fd_theme') || 'light',
|
||||||
// Account form
|
// Account form
|
||||||
accountForm: {login:'{{ user.login or "" }}', full_name:'{{ user.full_name or "" }}', email:'{{ user.email or "" }}', password:''},
|
accountForm: {login:'{{ user.login or "" }}', full_name:'{{ user.full_name or "" }}', email:'{{ user.email or "" }}', password:''},
|
||||||
accountSaving: false,
|
accountSaving: false,
|
||||||
@@ -739,26 +739,6 @@ document.addEventListener('alpine:init', function() {
|
|||||||
applyTheme() {
|
applyTheme() {
|
||||||
localStorage.setItem('fd_theme', this.theme);
|
localStorage.setItem('fd_theme', this.theme);
|
||||||
document.documentElement.setAttribute('data-theme', this.theme);
|
document.documentElement.setAttribute('data-theme', this.theme);
|
||||||
if (this.theme === 'light') {
|
|
||||||
document.documentElement.style.setProperty('--bg-primary', '#FFFFFF');
|
|
||||||
document.documentElement.style.setProperty('--bg-secondary', '#F7F6F3');
|
|
||||||
document.documentElement.style.setProperty('--bg-tertiary', '#EDECE9');
|
|
||||||
document.documentElement.style.setProperty('--text', '#37352F');
|
|
||||||
document.documentElement.style.setProperty('--text-secondary', '#6B6966');
|
|
||||||
document.documentElement.style.setProperty('--text-dim', '#9B9A97');
|
|
||||||
document.documentElement.style.setProperty('--border', '#E0DFDC');
|
|
||||||
document.documentElement.style.setProperty('--border-strong', '#CCCBC8');
|
|
||||||
} else {
|
|
||||||
document.documentElement.style.removeProperty('--bg-primary');
|
|
||||||
document.documentElement.style.removeProperty('--bg-secondary');
|
|
||||||
document.documentElement.style.removeProperty('--bg-tertiary');
|
|
||||||
document.documentElement.style.removeProperty('--text');
|
|
||||||
document.documentElement.style.removeProperty('--text-secondary');
|
|
||||||
document.documentElement.style.removeProperty('--text-dim');
|
|
||||||
document.documentElement.style.removeProperty('--border');
|
|
||||||
document.documentElement.style.removeProperty('--border-strong');
|
|
||||||
document.documentElement.setAttribute('data-theme', 'dark');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
saveDefaultView() {
|
saveDefaultView() {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<!-- Filters -->
|
<!-- Filters -->
|
||||||
<div style="display:flex; gap:8px; margin-bottom:16px;">
|
<div style="display:flex; gap:8px; margin-bottom:16px;">
|
||||||
<button class="trash-filter active">
|
<button class="trash-filter active">
|
||||||
<span style="color:#5b9bd5;">👤</span> Last edited by ▾
|
<span style="color:var(--accent);">👤</span> Last edited by ▾
|
||||||
</button>
|
</button>
|
||||||
<button class="trash-filter">
|
<button class="trash-filter">
|
||||||
<span>📁</span> In ▾
|
<span>📁</span> In ▾
|
||||||
|
|||||||
@@ -14,37 +14,37 @@
|
|||||||
<style>
|
<style>
|
||||||
.workspace-layout{max-width:900px;margin:0 auto;padding:32px 24px;}
|
.workspace-layout{max-width:900px;margin:0 auto;padding:32px 24px;}
|
||||||
.workspace-header{margin-bottom:24px;}
|
.workspace-header{margin-bottom:24px;}
|
||||||
.workspace-header h1{font-size:28px;font-weight:600;margin-bottom:8px;}
|
.workspace-header h1{font-size:28px;font-weight:600;margin-bottom:8px;color:var(--text-primary);}
|
||||||
.workspace-header p{color:rgba(255,255,255,.5);font-size:14px;}
|
.workspace-header p{color:var(--text-secondary);font-size:14px;}
|
||||||
|
|
||||||
.forge-section{margin-bottom:32px;}
|
.forge-section{margin-bottom:32px;}
|
||||||
.forge-section h2{font-size:14px;color:rgba(255,255,255,.45);text-transform:uppercase;letter-spacing:.5px;margin-bottom:12px;font-weight:500;display:flex;align-items:center;gap:8px;}
|
.forge-section h2{font-size:14px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.5px;margin-bottom:12px;font-weight:500;display:flex;align-items:center;gap:8px;}
|
||||||
.forge-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:500;}
|
.forge-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:500;}
|
||||||
.forge-badge.gitea{background:rgba(96,155,80,.15);color:#609b50;}
|
.forge-badge.gitea{background:rgba(96,155,80,.15);color:var(--green);}
|
||||||
.forge-badge.github{background:rgba(110,84,148,.15);color:#6e5494;}
|
.forge-badge.github{background:rgba(110,84,148,.15);color:var(--purple);}
|
||||||
.forge-badge.builtin{background:rgba(35,131,226,.15);color:#2383E2;}
|
.forge-badge.builtin{background:rgba(35,131,226,.15);color:var(--accent);}
|
||||||
|
|
||||||
.project-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;}
|
.project-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;}
|
||||||
.project-card{background:#222;border:1px solid rgba(255,255,255,.06);border-radius:12px;padding:16px;cursor:pointer;transition:background 150ms ease;}
|
.project-card{background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius-lg);padding:16px;cursor:pointer;transition:background var(--transition);}
|
||||||
.project-card:hover{background:#2A2A2A;border-color:rgba(255,255,255,.12);}
|
.project-card:hover{background:var(--bg-hover);border-color:var(--border-strong);}
|
||||||
.project-card-top{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:8px;}
|
.project-card-top{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:8px;}
|
||||||
.project-card-icon{font-size:20px;}
|
.project-card-icon{font-size:20px;}
|
||||||
.project-card-name{font-size:14px;font-weight:500;color:#fff;margin-bottom:4px;}
|
.project-card-name{font-size:14px;font-weight:500;color:var(--text-primary);margin-bottom:4px;}
|
||||||
.project-card-desc{font-size:12px;color:rgba(255,255,255,.4);margin-bottom:8px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
|
.project-card-desc{font-size:12px;color:var(--text-secondary);margin-bottom:8px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
|
||||||
.project-card-meta{display:flex;align-items:center;gap:8px;font-size:11px;color:rgba(255,255,255,.3);}
|
.project-card-meta{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--text-dim);}
|
||||||
|
|
||||||
.empty-state{text-align:center;padding:40px;color:rgba(255,255,255,.3);font-size:14px;}
|
.empty-state{text-align:center;padding:40px;color:var(--text-dim);font-size:14px;}
|
||||||
.empty-state .btn{margin-top:12px;display:inline-block;padding:8px 16px;background:#2383E2;color:#fff;border-radius:8px;text-decoration:none;font-size:13px;}
|
.empty-state .btn{margin-top:12px;display:inline-block;padding:8px 16px;background:var(--accent);color:#fff;border-radius:var(--radius-md);text-decoration:none;font-size:13px;}
|
||||||
|
|
||||||
.new-project-btn{display:inline-flex;align-items:center;gap:6px;padding:8px 16px;background:rgba(35,131,226,.15);color:#2383E2;border:1px solid rgba(35,131,226,.3);border-radius:8px;cursor:pointer;font-size:13px;}
|
.new-project-btn{display:inline-flex;align-items:center;gap:6px;padding:8px 16px;background:rgba(35,131,226,.15);color:var(--accent);border:1px solid rgba(35,131,226,.3);border-radius:var(--radius-md);cursor:pointer;font-size:13px;}
|
||||||
.new-project-btn:hover{background:rgba(35,131,226,.25);}
|
.new-project-btn:hover{background:rgba(35,131,226,.25);}
|
||||||
|
|
||||||
/* Modal */
|
/* Modal */
|
||||||
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;z-index:500;}
|
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.4);display:flex;align-items:center;justify-content:center;z-index:500;}
|
||||||
.modal-box{background:#1F1F1F;border:1px solid rgba(255,255,255,.08);border-radius:18px;padding:32px;width:400px;max-width:90vw;}
|
.modal-box{background:var(--bg-modal);border:1px solid var(--border);border-radius:var(--radius-lg);padding:32px;width:400px;max-width:90vw;box-shadow:var(--shadow-modal);}
|
||||||
.modal-box h3{font-size:16px;margin-bottom:16px;}
|
.modal-box h3{font-size:16px;margin-bottom:16px;color:var(--text-primary);}
|
||||||
.modal-input{width:100%;padding:10px 12px;background:#2A2A2A;border:1px solid rgba(255,255,255,.1);border-radius:8px;color:#fff;font-size:14px;margin-bottom:12px;outline:none;}
|
.modal-input{width:100%;padding:10px 12px;background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius-md);color:var(--text-primary);font-size:14px;margin-bottom:12px;outline:none;}
|
||||||
.modal-input:focus{border-color:#2383E2;}
|
.modal-input:focus{border-color:var(--accent);}
|
||||||
.modal-actions{display:flex;gap:8px;justify-content:flex-end;}
|
.modal-actions{display:flex;gap:8px;justify-content:flex-end;}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -72,8 +72,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="project-card" style="border:1px dashed rgba(255,255,255,.1);display:flex;align-items:center;justify-content:center;min-height:100px" @click="showCreateModal = true">
|
<div class="project-card" style="border:1px dashed var(--border-strong);display:flex;align-items:center;justify-content:center;min-height:100px" @click="showCreateModal = true">
|
||||||
<span style="color:rgba(255,255,255,.3);font-size:24px">+</span>
|
<span style="color:var(--text-dim);font-size:24px">+</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
.org-tab:hover{background:var(--bg-tertiary);color:var(--text);}
|
.org-tab:hover{background:var(--bg-tertiary);color:var(--text);}
|
||||||
.org-tab.active{background:var(--accent);color:#fff;border-color:var(--accent);}
|
.org-tab.active{background:var(--accent);color:#fff;border-color:var(--accent);}
|
||||||
.org-tab-badge{font-size:10px;padding:0 5px;border-radius:8px;background:rgba(255,255,255,.15);}
|
.org-tab-badge{font-size:10px;padding:0 5px;border-radius:8px;background:rgba(255,255,255,.15);}
|
||||||
|
[data-theme="light"] .org-tab-badge{background:rgba(0,0,0,.08);}
|
||||||
.search-input:focus{border-color:var(--accent);}
|
.search-input:focus{border-color:var(--accent);}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
BIN
Binary file not shown.
+507
-184
@@ -1,35 +1,41 @@
|
|||||||
/* ═══════════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════════
|
||||||
FlowDeck Notion UI — Design System
|
FlowDeck Notion UI — Design System
|
||||||
Référence: Notion Dark Mode (analysé 2026-07-08)
|
Référence: Notion Light Mode (par défaut)
|
||||||
Couleurs: #191919 fond, #222 sidebar, #333 actif, #FFF texte
|
|
||||||
═══════════════════════════════════════════════════════════ */
|
═══════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||||
|
|
||||||
|
/* ===== LIGHT THEME (default) ===== */
|
||||||
:root {
|
:root {
|
||||||
/* ===== Couleurs Notion Dark Mode ===== */
|
--bg-primary: #FFFFFF;
|
||||||
--bg-primary: #191919;
|
--bg-secondary: #FBFBFA;
|
||||||
--bg-secondary: #1f1f1f;
|
--bg-sidebar: #F7F6F3;
|
||||||
--bg-sidebar: #222222;
|
--bg-tertiary: #EDEDEC;
|
||||||
--bg-tertiary: #333333;
|
--bg-hover: #EFEFED;
|
||||||
--bg-hover: #2a2a2a;
|
--bg-active: #E8E7E4;
|
||||||
--bg-active: #2F2E2E;
|
--bg-modal: #FFFFFF;
|
||||||
--bg-modal: #252525;
|
|
||||||
|
|
||||||
--text-primary: #FFFFFF;
|
--text-primary: #37352F;
|
||||||
--text-secondary: #A0A0A0;
|
--text-secondary: #6B6B6B;
|
||||||
--text-dim: #6B6B6B;
|
--text-dim: #9B9A97;
|
||||||
--text-muted: #4A4A4A;
|
--text-muted: #B0B0B0;
|
||||||
|
--text: #37352F;
|
||||||
|
|
||||||
--border: #333333;
|
--border: #EDEDED;
|
||||||
--border-color: rgba(255,255,255,0.06);
|
--border-color: rgba(0,0,0,0.06);
|
||||||
--border-light: #3a3a3a;
|
--border-light: #EDEDEC;
|
||||||
|
--border-strong: #D3D3D0;
|
||||||
|
|
||||||
/* ===== Couleurs d'accent Notion ===== */
|
/* ===== Couleurs d'accent Notion ===== */
|
||||||
|
--accent: #2383E2;
|
||||||
|
--accent-hover: #1a6bc0;
|
||||||
|
--danger: #E03E3E;
|
||||||
|
--danger-hover: #C93535;
|
||||||
|
|
||||||
--blue: #3366CC;
|
--blue: #3366CC;
|
||||||
--blue-bg: #1a2744;
|
--blue-bg: #E8F0FE;
|
||||||
--green: #00CC66;
|
--green: #00CC66;
|
||||||
--green-bg: #1a332a;
|
--green-bg: #E6F4EA;
|
||||||
--yellow: #FFD700;
|
--yellow: #FFD700;
|
||||||
--orange: #D9730D;
|
--orange: #D9730D;
|
||||||
--red: #E03E3E;
|
--red: #E03E3E;
|
||||||
@@ -42,13 +48,21 @@
|
|||||||
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
--font-mono: 'SF Mono', 'Fira Code', monospace;
|
--font-mono: 'SF Mono', 'Fira Code', monospace;
|
||||||
|
|
||||||
|
--font-h1: 40px;
|
||||||
|
--font-h2: 30px;
|
||||||
|
--font-h3: 24px;
|
||||||
|
--font-body: 16px;
|
||||||
|
--font-small: 14px;
|
||||||
|
--font-tiny: 12px;
|
||||||
|
|
||||||
/* ===== Dimensions ===== */
|
/* ===== Dimensions ===== */
|
||||||
--sidebar-width: 240px;
|
--sidebar-width: 240px;
|
||||||
--sidebar-collapsed: 0px;
|
--sidebar-collapsed: 0px;
|
||||||
--topbar-height: 40px;
|
--topbar-height: 45px;
|
||||||
--radius-sm: 4px;
|
--radius-sm: 3px;
|
||||||
--radius-md: 6px;
|
--radius-md: 4px;
|
||||||
--radius-lg: 8px;
|
--radius-lg: 6px;
|
||||||
|
--radius: 6px;
|
||||||
|
|
||||||
/* ===== Espacements ===== */
|
/* ===== Espacements ===== */
|
||||||
--space-xs: 4px;
|
--space-xs: 4px;
|
||||||
@@ -57,27 +71,47 @@
|
|||||||
--space-lg: 16px;
|
--space-lg: 16px;
|
||||||
--space-xl: 24px;
|
--space-xl: 24px;
|
||||||
|
|
||||||
|
/* ===== Ombres ===== */
|
||||||
|
--shadow-popover: 0 1px 2px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.08);
|
||||||
|
--shadow-modal: 0 12px 32px rgba(0,0,0,0.12);
|
||||||
|
--shadow-drag: 0 8px 24px rgba(0,0,0,0.12);
|
||||||
|
|
||||||
/* ===== Transitions ===== */
|
/* ===== Transitions ===== */
|
||||||
--transition: 0.15s ease;
|
--transition: 0.1s ease;
|
||||||
|
--transition-slow: 0.2s ease;
|
||||||
|
|
||||||
|
/* ===== Toast colors ===== */
|
||||||
|
--toast-success-bg: #0F7B6C;
|
||||||
|
--toast-success-text: #FFFFFF;
|
||||||
|
--toast-error-bg: #E03E3E;
|
||||||
|
--toast-error-text: #FFFFFF;
|
||||||
|
--toast-info-bg: #2383E2;
|
||||||
|
--toast-info-text: #FFFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="light"] {
|
/* ===== DARK THEME ===== */
|
||||||
--bg-primary: #FFFFFF;
|
[data-theme="dark"] {
|
||||||
--bg-secondary: #FBFBFA;
|
--bg-primary: #191919;
|
||||||
--bg-sidebar: #F7F6F3;
|
--bg-secondary: #1F1F1F;
|
||||||
--bg-tertiary: #EDEDEC;
|
--bg-sidebar: #202020;
|
||||||
--bg-hover: #EFEFEE;
|
--bg-tertiary: #2D2D2D;
|
||||||
--bg-active: #E8E7E4;
|
--bg-hover: #2A2A2A;
|
||||||
--bg-modal: #FFFFFF;
|
--bg-active: #2F2E2E;
|
||||||
--text-primary: #1A1A1A;
|
--bg-modal: #252525;
|
||||||
--text-secondary: #6B6B6B;
|
|
||||||
--text-dim: #9B9B9B;
|
--text-primary: #FFFFFF;
|
||||||
--text-muted: #B0B0B0;
|
--text-secondary: #A0A0A0;
|
||||||
--border: #E0E0E0;
|
--text-dim: #6B6B6B;
|
||||||
--border-color: rgba(0,0,0,0.06);
|
--text-muted: #4A4A4A;
|
||||||
--border-light: #EDEDEC;
|
--text: #FFFFFF;
|
||||||
--blue-bg: #E8F0FE;
|
|
||||||
--green-bg: #E6F4EA;
|
--border: #333333;
|
||||||
|
--border-color: rgba(255,255,255,0.06);
|
||||||
|
--border-light: #3a3a3a;
|
||||||
|
--border-strong: #4A4A4A;
|
||||||
|
|
||||||
|
--blue-bg: #1a2744;
|
||||||
|
--green-bg: #1a332a;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== Reset & Base ===== */
|
/* ===== Reset & Base ===== */
|
||||||
@@ -217,7 +251,7 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
border: 1px solid var(--border-strong);
|
border: 1px solid var(--border-strong);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 12px 32px rgba(0,0,0,.6);
|
box-shadow: var(--shadow-modal);
|
||||||
z-index: 400;
|
z-index: 400;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -424,8 +458,6 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
|
|
||||||
.sidebar-item.active {
|
.sidebar-item.active {
|
||||||
background: var(--bg-active);
|
background: var(--bg-active);
|
||||||
border-left: 2px solid var(--accent);
|
|
||||||
padding-left: 2px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tree toggle button (▶/▼ chevron) */
|
/* Tree toggle button (▶/▼ chevron) */
|
||||||
@@ -869,14 +901,16 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
/* ── Dropdown panel (reusable) ── */
|
/* ── Dropdown panel (reusable) ── */
|
||||||
.dropdown-panel {
|
.dropdown-panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
display: none;
|
display: none;
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-modal);
|
||||||
border: 1px solid var(--border-strong);
|
border: 1px solid var(--border);
|
||||||
border-radius: 8px;
|
border-radius: var(--radius-lg);
|
||||||
box-shadow: 0 8px 24px rgba(0,0,0,.4);
|
box-shadow: var(--shadow-popover);
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
padding: 4px;
|
padding: 4px 0;
|
||||||
min-width: 160px;
|
min-width: 180px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-panel.visible {
|
.dropdown-panel.visible {
|
||||||
@@ -886,13 +920,13 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
.dropdown-option {
|
.dropdown-option {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: var(--space-sm);
|
||||||
padding: 6px 10px;
|
padding: 6px 12px;
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
|
cursor: pointer;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
transition: background var(--transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-option:hover {
|
.dropdown-option:hover {
|
||||||
@@ -925,7 +959,7 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
.page-cover {
|
.page-cover {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 160px;
|
height: 160px;
|
||||||
background: linear-gradient(135deg, #2a2a2a, #333333);
|
background: linear-gradient(135deg, var(--bg-tertiary), var(--border-strong));
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
@@ -1528,24 +1562,7 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
color: var(--red);
|
color: var(--red);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Panel dropdown */
|
/* Panel dropdown helpers */
|
||||||
.dropdown-panel {
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 0;
|
|
||||||
background: var(--bg-modal);
|
|
||||||
border: 1px solid var(--border-light);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
|
|
||||||
min-width: 220px;
|
|
||||||
z-index: 100;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-panel.visible {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-search {
|
.dropdown-search {
|
||||||
padding: var(--space-sm);
|
padding: var(--space-sm);
|
||||||
}
|
}
|
||||||
@@ -1680,7 +1697,7 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
.modal-overlay {
|
.modal-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: rgba(0,0,0,0.6);
|
background: rgba(0,0,0,0.4);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -1689,8 +1706,9 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
|
|
||||||
.modal-container {
|
.modal-container {
|
||||||
background: var(--bg-modal);
|
background: var(--bg-modal);
|
||||||
|
border: 1px solid var(--border);
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
box-shadow: 0 16px 48px rgba(0,0,0,0.5);
|
box-shadow: var(--shadow-modal);
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
@@ -1775,7 +1793,7 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
background: var(--bg-tertiary); transition: background 0.15s;
|
background: var(--bg-tertiary); transition: background 0.15s;
|
||||||
}
|
}
|
||||||
.trash-filter:hover { background: var(--bg-hover); }
|
.trash-filter:hover { background: var(--bg-hover); }
|
||||||
.trash-filter.active { color: #5b9bd5; background: rgba(91,155,213,0.1); }
|
.trash-filter.active { color: var(--accent); background: rgba(35,131,226,0.1); }
|
||||||
.trash-item {
|
.trash-item {
|
||||||
display: flex; align-items: center; gap: 12px;
|
display: flex; align-items: center; gap: 12px;
|
||||||
padding: 10px 12px; border-radius: 6px; transition: background 0.15s;
|
padding: 10px 12px; border-radius: 6px; transition: background 0.15s;
|
||||||
@@ -1802,7 +1820,7 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
|
|
||||||
/* Sortable ghost */
|
/* Sortable ghost */
|
||||||
.sortable-ghost { opacity: 0.4; }
|
.sortable-ghost { opacity: 0.4; }
|
||||||
.sortable-drag { box-shadow: 0 8px 24px rgba(0,0,0,0.4) !important; }
|
.sortable-drag { box-shadow: var(--shadow-drag) !important; }
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════════
|
||||||
NOTION-STYLE BLOCK EDITOR
|
NOTION-STYLE BLOCK EDITOR
|
||||||
@@ -1885,7 +1903,7 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
transition: background 0.1s ease;
|
transition: background 0.1s ease;
|
||||||
}
|
}
|
||||||
.block-wrapper:hover {
|
.block-wrapper:hover {
|
||||||
background: rgba(255,255,255,0.02);
|
background: rgba(0,0,0,0.02);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 6-dot drag handle */
|
/* 6-dot drag handle */
|
||||||
@@ -1976,7 +1994,7 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
}
|
}
|
||||||
.block-content.empty::before {
|
.block-content.empty::before {
|
||||||
content: attr(data-placeholder);
|
content: attr(data-placeholder);
|
||||||
color: #9B9A97;
|
color: var(--text-dim);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
@@ -2054,12 +2072,6 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
min-height: 26px;
|
min-height: 26px;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
.block-content.empty::before {
|
|
||||||
content: attr(data-placeholder);
|
|
||||||
color: #9B9A97;
|
|
||||||
pointer-events: none;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Toggle */
|
/* Toggle */
|
||||||
.block-toggle {
|
.block-toggle {
|
||||||
@@ -2095,12 +2107,6 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
min-height: 26px;
|
min-height: 26px;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
.block-content.empty::before {
|
|
||||||
content: attr(data-placeholder);
|
|
||||||
color: #9B9A97;
|
|
||||||
pointer-events: none;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Quote */
|
/* Quote */
|
||||||
.block-quote {
|
.block-quote {
|
||||||
@@ -2142,7 +2148,7 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
}
|
}
|
||||||
.block-code code:empty::before {
|
.block-code code:empty::before {
|
||||||
content: 'Type code...';
|
content: 'Type code...';
|
||||||
color: #9B9A97;
|
color: var(--text-dim);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Divider */
|
/* Divider */
|
||||||
@@ -2173,12 +2179,6 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
min-height: 24px;
|
min-height: 24px;
|
||||||
}
|
}
|
||||||
.block-content.empty::before {
|
|
||||||
content: attr(data-placeholder);
|
|
||||||
color: #9B9A97;
|
|
||||||
pointer-events: none;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Image block */
|
/* Image block */
|
||||||
.block-image-wrapper {
|
.block-image-wrapper {
|
||||||
@@ -2445,44 +2445,44 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
.share-btn { font-weight: 500; }
|
.share-btn { font-weight: 500; }
|
||||||
.page-action-btn .chevron-down { font-size: 9px; opacity: 0.5; margin-left: 2px; }
|
.page-action-btn .chevron-down { font-size: 9px; opacity: 0.5; margin-left: 2px; }
|
||||||
.star-btn { font-size: 16px; padding: 4px 8px; }
|
.star-btn { font-size: 16px; padding: 4px 8px; }
|
||||||
.star-btn .starred { color: #f5c542; }
|
.star-btn .starred { color: var(--yellow); }
|
||||||
|
|
||||||
/* ═══════════ Share / Publish Dialog ═══════════ */
|
/* ═══════════ Share / Publish Dialog ═══════════ */
|
||||||
.share-dialog {
|
.share-dialog {
|
||||||
position: absolute; top: calc(100% + 8px); right: 72px;
|
position: absolute; top: calc(100% + 8px); right: 72px;
|
||||||
width: 440px; max-height: 65vh; overflow-y: auto;
|
width: 440px; max-height: 65vh; overflow-y: auto;
|
||||||
background: #1F1F1F; border: 1px solid rgba(255,255,255,.08);
|
background: var(--bg-modal); border: 1px solid var(--border);
|
||||||
border-radius: 18px; box-shadow: 0 12px 48px rgba(0,0,0,0.7);
|
border-radius: var(--radius-lg); box-shadow: var(--shadow-modal);
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
}
|
}
|
||||||
/* Header with tabs */
|
/* Header with tabs */
|
||||||
.sd-header {
|
.sd-header {
|
||||||
display: flex; align-items: center; height: 56px;
|
display: flex; align-items: center; height: 56px;
|
||||||
padding: 0 8px; border-bottom: 1px solid rgba(255,255,255,.06);
|
padding: 0 8px; border-bottom: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
.sd-tab {
|
.sd-tab {
|
||||||
height: 56px; padding: 0 14px; display: flex; align-items: center; gap: 6px;
|
height: 56px; padding: 0 14px; display: flex; align-items: center; gap: 6px;
|
||||||
font-size: 14px; font-weight: 500; cursor: pointer;
|
font-size: 14px; font-weight: 500; cursor: pointer;
|
||||||
background: none; border: none;
|
background: none; border: none;
|
||||||
color: rgba(255,255,255,.5); position: relative;
|
color: var(--text-dim); position: relative;
|
||||||
transition: color 150ms ease;
|
transition: color var(--transition);
|
||||||
}
|
}
|
||||||
.sd-tab.active { color: #fff; }
|
.sd-tab.active { color: var(--text-primary); }
|
||||||
.sd-tab.active::after {
|
.sd-tab.active::after {
|
||||||
content: ''; position: absolute; bottom: 0; left: 14px; right: 14px;
|
content: ''; position: absolute; bottom: 0; left: 14px; right: 14px;
|
||||||
height: 2px; background: #fff; transition: 200ms ease;
|
height: 2px; background: var(--text-primary); transition: var(--transition);
|
||||||
}
|
}
|
||||||
.sd-tab:hover { color: rgba(255,255,255,.8); }
|
.sd-tab:hover { color: var(--text-secondary); }
|
||||||
.sd-badge {
|
.sd-badge {
|
||||||
width: 6px; height: 6px; border-radius: 999px; background: #2383E2;
|
width: 6px; height: 6px; border-radius: 999px; background: var(--accent);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
.sd-close {
|
.sd-close {
|
||||||
margin-left: auto; background: none; border: none;
|
margin-left: auto; background: none; border: none;
|
||||||
color: rgba(255,255,255,.5); font-size: 16px; cursor: pointer;
|
color: var(--text-dim); font-size: 16px; cursor: pointer;
|
||||||
padding: 8px; border-radius: 6px;
|
padding: 8px; border-radius: var(--radius-sm);
|
||||||
}
|
}
|
||||||
.sd-close:hover { background: rgba(255,255,255,.06); color: #fff; }
|
.sd-close:hover { background: var(--bg-hover); color: var(--text-primary); }
|
||||||
|
|
||||||
/* Body */
|
/* Body */
|
||||||
.sd-body { padding: 16px 20px; }
|
.sd-body { padding: 16px 20px; }
|
||||||
@@ -2491,60 +2491,60 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
.sd-invite-row { display: flex; gap: 8px; margin-bottom: 12px; }
|
.sd-invite-row { display: flex; gap: 8px; margin-bottom: 12px; }
|
||||||
.sd-input {
|
.sd-input {
|
||||||
flex: 1; height: 40px; padding: 0 14px;
|
flex: 1; height: 40px; padding: 0 14px;
|
||||||
background: #262626; border: 1px solid rgba(255,255,255,.06);
|
background: var(--bg-secondary); border: 1px solid var(--border);
|
||||||
border-radius: 10px; color: #fff; font-size: 13px; outline: none;
|
border-radius: var(--radius-md); color: var(--text-primary); font-size: 13px; outline: none;
|
||||||
transition: border-color 150ms, box-shadow 150ms;
|
transition: border-color var(--transition), box-shadow var(--transition);
|
||||||
}
|
}
|
||||||
.sd-input::placeholder { color: rgba(255,255,255,.45); }
|
.sd-input::placeholder { color: var(--text-dim); }
|
||||||
.sd-input:focus { border-color: #2383E2; box-shadow: 0 0 0 1px #2383E2; }
|
.sd-input:focus { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
|
||||||
.sd-btn-primary {
|
.sd-btn-primary {
|
||||||
height: 40px; padding: 0 16px; background: #2383E2; color: #fff;
|
height: 40px; padding: 0 16px; background: var(--accent); color: #fff;
|
||||||
border: none; border-radius: 10px; font-size: 13px; font-weight: 500;
|
border: none; border-radius: var(--radius-md); font-size: 13px; font-weight: 500;
|
||||||
cursor: pointer; transition: background 150ms ease; white-space: nowrap;
|
cursor: pointer; transition: background var(--transition); white-space: nowrap;
|
||||||
}
|
}
|
||||||
.sd-btn-primary:hover { background: #2C8CEB; }
|
.sd-btn-primary:hover { background: var(--accent-hover); }
|
||||||
.sd-btn-full { width: 100%; height: 46px; font-size: 14px; }
|
.sd-btn-full { width: 100%; height: 46px; font-size: 14px; }
|
||||||
|
|
||||||
/* Context card */
|
/* Context card */
|
||||||
.sd-context-card {
|
.sd-context-card {
|
||||||
background: #2B2B2B; border-radius: 12px;
|
background: var(--bg-secondary); border-radius: var(--radius-md);
|
||||||
padding: 12px; font-size: 13px; color: rgba(255,255,255,.55);
|
padding: 12px; font-size: 13px; color: var(--text-secondary);
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Section */
|
/* Section */
|
||||||
.sd-section { margin: 8px 0; }
|
.sd-section { margin: 8px 0; }
|
||||||
.sd-section-title {
|
.sd-section-title {
|
||||||
font-size: 12px; color: rgba(255,255,255,.55); margin-bottom: 6px;
|
font-size: 12px; color: var(--text-secondary); margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
.sd-sep { height: 1px; background: rgba(255,255,255,.06); margin: 12px 0; }
|
.sd-sep { height: 1px; background: var(--border); margin: 12px 0; }
|
||||||
|
|
||||||
/* Access button */
|
/* Access button */
|
||||||
.sd-access-btn {
|
.sd-access-btn {
|
||||||
width: 100%; display: flex; align-items: center; justify-content: space-between;
|
width: 100%; display: flex; align-items: center; justify-content: space-between;
|
||||||
padding: 10px 14px; background: rgba(255,255,255,.04);
|
padding: 10px 14px; background: var(--bg-secondary);
|
||||||
border: 1px solid rgba(255,255,255,.06); border-radius: 10px;
|
border: 1px solid var(--border); border-radius: var(--radius-md);
|
||||||
color: #fff; font-size: 13px; cursor: pointer;
|
color: var(--text-primary); font-size: 13px; cursor: pointer;
|
||||||
}
|
}
|
||||||
.sd-access-btn:hover { background: rgba(255,255,255,.08); }
|
.sd-access-btn:hover { background: var(--bg-hover); }
|
||||||
.sd-access-menu {
|
.sd-access-menu {
|
||||||
position: absolute; top: calc(100% + 2px); left: 0; right: 0;
|
position: absolute; top: calc(100% + 2px); left: 0; right: 0;
|
||||||
background: #2A2A2A !important; border: 1px solid rgba(255,255,255,.12);
|
background: var(--bg-modal) !important; border: 1px solid var(--border);
|
||||||
border-radius: 14px; box-shadow: 0 8px 24px rgba(0,0,0,0.6);
|
border-radius: var(--radius-lg); box-shadow: var(--shadow-popover);
|
||||||
z-index: 999; padding: 4px 0;
|
z-index: 999; padding: 4px 0;
|
||||||
}
|
}
|
||||||
.sd-access-option {
|
.sd-access-option {
|
||||||
padding: 8px 14px; font-size: 13px; color: #fff;
|
padding: 8px 14px; font-size: 13px; color: var(--text-primary);
|
||||||
cursor: pointer; display: flex; align-items: center; gap: 8px;
|
cursor: pointer; display: flex; align-items: center; gap: 8px;
|
||||||
}
|
}
|
||||||
.sd-access-option:hover { background: rgba(255,255,255,.08); }
|
.sd-access-option:hover { background: var(--bg-hover); }
|
||||||
.sd-access-option.active { background: rgba(35,131,226,.15); }
|
.sd-access-option.active { background: rgba(35,131,226,.15); }
|
||||||
|
|
||||||
/* Permissions */
|
/* Permissions */
|
||||||
.sd-select-sm {
|
.sd-select-sm {
|
||||||
padding: 4px 8px; background: rgba(255,255,255,.04);
|
padding: 4px 8px; background: var(--bg-secondary);
|
||||||
border: 1px solid rgba(255,255,255,.06); border-radius: 6px;
|
border: 1px solid var(--border); border-radius: var(--radius-sm);
|
||||||
color: #fff; font-size: 12px; outline: none;
|
color: var(--text-primary); font-size: 12px; outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Participants */
|
/* Participants */
|
||||||
@@ -2556,49 +2556,49 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
.sd-participant-info { display: flex; align-items: center; gap: 10px; }
|
.sd-participant-info { display: flex; align-items: center; gap: 10px; }
|
||||||
.sd-avatar {
|
.sd-avatar {
|
||||||
width: 32px; height: 32px; border-radius: 50%;
|
width: 32px; height: 32px; border-radius: 50%;
|
||||||
background: #2383E2; color: #fff;
|
background: var(--accent); color: #fff;
|
||||||
display: flex; align-items: center; justify-content: center;
|
display: flex; align-items: center; justify-content: center;
|
||||||
font-size: 13px; font-weight: 600;
|
font-size: 13px; font-weight: 600;
|
||||||
}
|
}
|
||||||
.sd-participant-name { font-size: 14px; font-weight: 500; color: #fff; }
|
.sd-participant-name { font-size: 14px; font-weight: 500; color: var(--text-primary); }
|
||||||
.sd-participant-email { font-size: 13px; color: rgba(255,255,255,.6); }
|
.sd-participant-email { font-size: 13px; color: var(--text-secondary); }
|
||||||
.sd-perm-btn {
|
.sd-perm-btn {
|
||||||
padding: 4px 10px; background: rgba(255,255,255,.04);
|
padding: 4px 10px; background: var(--bg-secondary);
|
||||||
border: 1px solid rgba(255,255,255,.06); border-radius: 6px;
|
border: 1px solid var(--border); border-radius: var(--radius-sm);
|
||||||
color: rgba(255,255,255,.6); font-size: 12px; cursor: pointer;
|
color: var(--text-secondary); font-size: 12px; cursor: pointer;
|
||||||
}
|
}
|
||||||
.sd-perm-btn:hover { background: rgba(255,255,255,.08); }
|
.sd-perm-btn:hover { background: var(--bg-hover); }
|
||||||
.sd-perm-menu {
|
.sd-perm-menu {
|
||||||
position: absolute; top: 100%; right: 0; width: 280px;
|
position: absolute; top: 100%; right: 0; width: 280px;
|
||||||
background: #2A2A2A; border: 1px solid rgba(255,255,255,.08);
|
background: var(--bg-modal); border: 1px solid var(--border);
|
||||||
border-radius: 14px; box-shadow: 0 8px 24px rgba(0,0,0,0.5);
|
border-radius: var(--radius-lg); box-shadow: var(--shadow-popover);
|
||||||
z-index: 211; padding: 4px 0;
|
z-index: 211; padding: 4px 0;
|
||||||
}
|
}
|
||||||
.sd-perm-option {
|
.sd-perm-option {
|
||||||
padding: 10px 16px; cursor: pointer;
|
padding: 10px 16px; cursor: pointer;
|
||||||
display: flex; flex-direction: column;
|
display: flex; flex-direction: column;
|
||||||
}
|
}
|
||||||
.sd-perm-option span:first-child { font-size: 14px; color: #fff; }
|
.sd-perm-option span:first-child { font-size: 14px; color: var(--text-primary); }
|
||||||
.sd-perm-option:hover { background: #343434; }
|
.sd-perm-option:hover { background: var(--bg-hover); }
|
||||||
.sd-perm-option.active { background: rgba(35,131,226,.15); }
|
.sd-perm-option.active { background: rgba(35,131,226,.15); }
|
||||||
.sd-perm-option.danger:hover { background: rgba(224,62,62,.15); }
|
.sd-perm-option.danger:hover { background: rgba(224,62,62,.15); }
|
||||||
.sd-perm-option.danger span:first-child { color: #e03e3e; }
|
.sd-perm-option.danger span:first-child { color: var(--danger); }
|
||||||
.sd-perm-desc { font-size: 12px !important; color: rgba(255,255,255,.45) !important; margin-top: 2px; }
|
.sd-perm-desc { font-size: 12px !important; color: var(--text-dim) !important; margin-top: 2px; }
|
||||||
.sd-perm-sep { height: 1px; background: rgba(255,255,255,.06); margin: 4px 0; }
|
.sd-perm-sep { height: 1px; background: var(--border); margin: 4px 0; }
|
||||||
|
|
||||||
/* Footer */
|
/* Footer */
|
||||||
.sd-footer {
|
.sd-footer {
|
||||||
display: flex; align-items: center; justify-content: space-between;
|
display: flex; align-items: center; justify-content: space-between;
|
||||||
}
|
}
|
||||||
.sd-footer-link { font-size: 13px; color: rgba(255,255,255,.55); text-decoration: none; }
|
.sd-footer-link { font-size: 13px; color: var(--text-secondary); text-decoration: none; }
|
||||||
.sd-footer-link:hover { color: #fff; }
|
.sd-footer-link:hover { color: var(--text-primary); }
|
||||||
.sd-footer-action {
|
.sd-footer-action {
|
||||||
padding: 6px 14px; background: rgba(255,255,255,.06);
|
padding: 6px 14px; background: var(--bg-secondary);
|
||||||
border: 1px solid rgba(255,255,255,.06); border-radius: 10px;
|
border: 1px solid var(--border); border-radius: var(--radius-md);
|
||||||
color: #fff; font-size: 13px; cursor: pointer;
|
color: var(--text-primary); font-size: 13px; cursor: pointer;
|
||||||
display: flex; align-items: center; gap: 6px;
|
display: flex; align-items: center; gap: 6px;
|
||||||
}
|
}
|
||||||
.sd-footer-action:hover { background: rgba(255,255,255,.1); }
|
.sd-footer-action:hover { background: var(--bg-hover); }
|
||||||
|
|
||||||
/* Publish tab */
|
/* Publish tab */
|
||||||
.sd-publish-hero {
|
.sd-publish-hero {
|
||||||
@@ -2606,30 +2606,30 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
text-align: center; padding: 16px 0;
|
text-align: center; padding: 16px 0;
|
||||||
}
|
}
|
||||||
.sd-publish-preview {
|
.sd-publish-preview {
|
||||||
width: 100%; height: 200px; background: #262626;
|
width: 100%; height: 200px; background: var(--bg-secondary);
|
||||||
border-radius: 12px; margin-bottom: 20px;
|
border-radius: var(--radius-md); margin-bottom: 20px;
|
||||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||||
}
|
}
|
||||||
.sd-preview-bar {
|
.sd-preview-bar {
|
||||||
width: 100%; height: 32px; background: rgba(255,255,255,.04);
|
width: 100%; height: 32px; background: var(--bg-tertiary);
|
||||||
border-radius: 12px 12px 0 0; display: flex; align-items: center; padding: 0 12px;
|
border-radius: var(--radius-md) var(--radius-md) 0 0; display: flex; align-items: center; padding: 0 12px;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
.sd-preview-title { font-size: 16px; color: rgba(255,255,255,.6); margin-top: 20px; }
|
.sd-preview-title { font-size: 16px; color: var(--text-secondary); margin-top: 20px; }
|
||||||
.sd-publish-heading { font-size: 18px; font-weight: 600; color: #fff; margin: 0 0 8px; }
|
.sd-publish-heading { font-size: 18px; font-weight: 600; color: var(--text-primary); margin: 0 0 8px; }
|
||||||
.sd-publish-desc { font-size: 13px; color: rgba(255,255,255,.55); margin: 0 0 16px; line-height: 1.5; }
|
.sd-publish-desc { font-size: 13px; color: var(--text-secondary); margin: 0 0 16px; line-height: 1.5; }
|
||||||
.sd-publish-note { font-size: 12px; color: rgba(255,255,255,.35); margin-top: 12px; }
|
.sd-publish-note { font-size: 12px; color: var(--text-dim); margin-top: 12px; }
|
||||||
|
|
||||||
/* URL bar (after publish) */
|
/* URL bar (after publish) */
|
||||||
.sd-url-bar {
|
.sd-url-bar {
|
||||||
display: flex; align-items: center; gap: 8px;
|
display: flex; align-items: center; gap: 8px;
|
||||||
padding: 8px; background: rgba(255,255,255,.04);
|
padding: 8px; background: var(--bg-secondary);
|
||||||
border-radius: 10px;
|
border-radius: var(--radius-md);
|
||||||
}
|
}
|
||||||
.sd-url-input {
|
.sd-url-input {
|
||||||
flex: 1; height: 32px; padding: 0 10px;
|
flex: 1; height: 32px; padding: 0 10px;
|
||||||
background: transparent; border: none;
|
background: transparent; border: none;
|
||||||
color: rgba(255,255,255,.6); font-size: 13px; outline: none;
|
color: var(--text-secondary); font-size: 13px; outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Settings rows */
|
/* Settings rows */
|
||||||
@@ -2637,38 +2637,38 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
.sd-setting-row {
|
.sd-setting-row {
|
||||||
display: flex; align-items: center; justify-content: space-between;
|
display: flex; align-items: center; justify-content: space-between;
|
||||||
height: 40px; padding: 0 4px;
|
height: 40px; padding: 0 4px;
|
||||||
font-size: 14px; color: #fff; cursor: pointer;
|
font-size: 14px; color: var(--text-primary); cursor: pointer;
|
||||||
border-radius: 6px;
|
border-radius: var(--radius-sm);
|
||||||
}
|
}
|
||||||
.sd-setting-row:hover { background: #2F2F2F; }
|
.sd-setting-row:hover { background: var(--bg-hover); }
|
||||||
.sd-danger-btn {
|
.sd-danger-btn {
|
||||||
width: 100%; padding: 10px; background: none;
|
width: 100%; padding: 10px; background: none;
|
||||||
border: 1px solid rgba(255,255,255,.08); border-radius: 10px;
|
border: 1px solid var(--border); border-radius: var(--radius-md);
|
||||||
color: rgba(255,255,255,.55); font-size: 13px; cursor: pointer;
|
color: var(--text-secondary); font-size: 13px; cursor: pointer;
|
||||||
}
|
}
|
||||||
.sd-danger-btn:hover { background: rgba(224,62,62,.1); color: #e03e3e; }
|
.sd-danger-btn:hover { background: rgba(224,62,62,.1); color: var(--danger); }
|
||||||
|
|
||||||
/* Toast */
|
/* Toast */
|
||||||
.sd-toast {
|
.sd-toast {
|
||||||
position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
|
position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
|
||||||
background: #111; color: #fff; padding: 10px 18px;
|
background: var(--bg-secondary); color: var(--text-primary); padding: 10px 18px;
|
||||||
border-radius: 10px; font-size: 13px; z-index: 300;
|
border-radius: var(--radius-md); font-size: 13px; z-index: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── More menu dropdown ── */
|
/* ── More menu dropdown ── */
|
||||||
.more-menu {
|
.more-menu {
|
||||||
position: absolute; top: calc(100% + 4px); right: 0;
|
position: absolute; top: calc(100% + 4px); right: 0;
|
||||||
background: #2A2A2A; border: 1px solid rgba(255,255,255,.08);
|
background: var(--bg-modal); border: 1px solid var(--border);
|
||||||
border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.4);
|
border-radius: var(--radius-md); box-shadow: var(--shadow-popover);
|
||||||
min-width: 200px; z-index: 100; padding: 4px 0;
|
min-width: 200px; z-index: 100; padding: 4px 0;
|
||||||
}
|
}
|
||||||
.more-menu-item {
|
.more-menu-item {
|
||||||
padding: 8px 14px; font-size: 13px; color: #fff;
|
padding: 8px 14px; font-size: 13px; color: var(--text-primary);
|
||||||
cursor: pointer; display: flex; align-items: center; gap: 8px;
|
cursor: pointer; display: flex; align-items: center; gap: 8px;
|
||||||
}
|
}
|
||||||
.more-menu-item:hover { background: rgba(255,255,255,.06); }
|
.more-menu-item:hover { background: var(--bg-hover); }
|
||||||
.more-menu-item.danger { color: #e03e3e; }
|
.more-menu-item.danger { color: var(--danger); }
|
||||||
.more-menu-sep { height: 1px; background: rgba(255,255,255,.06); margin: 4px 0; }
|
.more-menu-sep { height: 1px; background: var(--border); margin: 4px 0; }
|
||||||
|
|
||||||
/* ── Toggle switch ── */
|
/* ── Toggle switch ── */
|
||||||
.toggle-switch { position: relative; display: inline-block; width: 36px; height: 20px; }
|
.toggle-switch { position: relative; display: inline-block; width: 36px; height: 20px; }
|
||||||
@@ -2676,15 +2676,15 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
.toggle-switch input { opacity: 0; width: 0; height: 0; }
|
.toggle-switch input { opacity: 0; width: 0; height: 0; }
|
||||||
.toggle-slider {
|
.toggle-slider {
|
||||||
position: absolute; inset: 0; cursor: pointer;
|
position: absolute; inset: 0; cursor: pointer;
|
||||||
background: #555; border-radius: 20px;
|
background: var(--text-dim); border-radius: 20px;
|
||||||
transition: background 150ms ease;
|
transition: background var(--transition);
|
||||||
}
|
}
|
||||||
.toggle-slider:before {
|
.toggle-slider:before {
|
||||||
content: ''; position: absolute; height: 16px; width: 16px;
|
content: ''; position: absolute; height: 16px; width: 16px;
|
||||||
left: 2px; bottom: 2px; background: #fff;
|
left: 2px; bottom: 2px; background: #fff;
|
||||||
border-radius: 50%; transition: transform 150ms ease;
|
border-radius: 50%; transition: transform var(--transition);
|
||||||
}
|
}
|
||||||
.toggle-switch input:checked + .toggle-slider { background: #2383E2; }
|
.toggle-switch input:checked + .toggle-slider { background: var(--accent); }
|
||||||
.toggle-switch input:checked + .toggle-slider:before { transform: translateX(16px); }
|
.toggle-switch input:checked + .toggle-slider:before { transform: translateX(16px); }
|
||||||
.toggle-switch.sm { width: 32px; height: 18px; }
|
.toggle-switch.sm { width: 32px; height: 18px; }
|
||||||
.toggle-switch.sm .toggle-slider:before { height: 14px; width: 14px; }
|
.toggle-switch.sm .toggle-slider:before { height: 14px; width: 14px; }
|
||||||
@@ -2980,3 +2980,326 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
|||||||
border-radius: 8px; color: var(--text-secondary); cursor: pointer;
|
border-radius: 8px; color: var(--text-secondary); cursor: pointer;
|
||||||
}
|
}
|
||||||
.sidebar-uncollapse-btn:hover { background: var(--bg-hover); color: var(--text); }
|
.sidebar-uncollapse-btn:hover { background: var(--bg-hover); color: var(--text); }
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════════════════
|
||||||
|
SHARED COMPONENTS (migrated from design-tokens.css)
|
||||||
|
═══════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
|
[x-cloak] { display: none !important; }
|
||||||
|
|
||||||
|
/* Skeleton */
|
||||||
|
.skeleton {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
animation: skeleton-pulse 1.5s ease-in-out infinite;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes skeleton-pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: .5; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-card {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 20px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-card .skeleton-line {
|
||||||
|
height: 14px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-card .skeleton-line:last-child { margin-bottom: 0; }
|
||||||
|
|
||||||
|
.skeleton-file {
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-line {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
animation: skeleton-pulse 1.5s ease-in-out infinite;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-table { overflow: hidden; }
|
||||||
|
|
||||||
|
.skeleton-table .skeleton-line {
|
||||||
|
height: 40px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toast */
|
||||||
|
.toast-container {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 24px;
|
||||||
|
right: 24px;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
max-width: 400px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0,0,0,.4);
|
||||||
|
pointer-events: auto;
|
||||||
|
animation: toast-slide-in 0.25s ease-out;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-success {
|
||||||
|
background: var(--toast-success-bg);
|
||||||
|
color: var(--toast-success-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-error {
|
||||||
|
background: var(--toast-error-bg);
|
||||||
|
color: var(--toast-error-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-info {
|
||||||
|
background: var(--toast-info-bg);
|
||||||
|
color: var(--toast-info-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes toast-slide-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(12px) scale(0.96);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-dim);
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
transition: background var(--transition), color var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover { background: var(--accent-hover); }
|
||||||
|
|
||||||
|
.btn-primary:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: rgba(255,80,80,.15);
|
||||||
|
color: var(--danger);
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover { background: rgba(255,80,80,.25); }
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
transition: background var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover { background: var(--bg-hover); }
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-dim);
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sm:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sm-danger {
|
||||||
|
color: var(--danger);
|
||||||
|
border-color: rgba(255,80,80,.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forms */
|
||||||
|
.settings-input {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
outline: none;
|
||||||
|
width: 100%;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-input:focus {
|
||||||
|
border-color: var(--accent);
|
||||||
|
box-shadow: 0 0 0 2px rgba(35,131,226,.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-select {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
min-width: 140px;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-select:focus { border-color: var(--accent); }
|
||||||
|
|
||||||
|
/* Dialog */
|
||||||
|
.dialog-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0,0,0,.4);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-box {
|
||||||
|
background: var(--bg-modal);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
padding: 24px;
|
||||||
|
width: 400px;
|
||||||
|
max-width: 90vw;
|
||||||
|
box-shadow: var(--shadow-modal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-box h3 {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
outline: none;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-input:focus { border-color: var(--accent); }
|
||||||
|
|
||||||
|
.dialog-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-box {
|
||||||
|
background: var(--bg-modal);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
padding: 24px;
|
||||||
|
min-width: 320px;
|
||||||
|
max-width: 90vw;
|
||||||
|
box-shadow: var(--shadow-modal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Empty state */
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
color: var(--text-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text utilities */
|
||||||
|
.text-dim { color: var(--text-dim); }
|
||||||
|
.text-xs { font-size: 12px; }
|
||||||
|
.text-secondary { color: var(--text-secondary); }
|
||||||
|
|
||||||
|
/* Password toggle */
|
||||||
|
.pw-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-toggle {
|
||||||
|
position: absolute;
|
||||||
|
right: 6px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 4px;
|
||||||
|
color: var(--text-dim);
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,500 +0,0 @@
|
|||||||
/* ═══════════════════════════════════════════════════════════
|
|
||||||
FlowDeck — Design Tokens & System
|
|
||||||
Theme variables, light/dark mode, skeletons, toasts, shared UI
|
|
||||||
═══════════════════════════════════════════════════════════ */
|
|
||||||
|
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
|
||||||
|
|
||||||
/* ═══════════ THEME VARIABLES (Dark — default) ═══════════ */
|
|
||||||
:root {
|
|
||||||
/* ===== Background ===== */
|
|
||||||
--bg-primary: #191919;
|
|
||||||
--bg-secondary: #1f1f1f;
|
|
||||||
--bg-sidebar: #222222;
|
|
||||||
--bg-tertiary: #333333;
|
|
||||||
--bg-hover: #2a2a2a;
|
|
||||||
--bg-active: #2F2E2E;
|
|
||||||
--bg-modal: #252525;
|
|
||||||
|
|
||||||
/* ===== Text ===== */
|
|
||||||
--text-primary: #FFFFFF;
|
|
||||||
--text-secondary: #A0A0A0;
|
|
||||||
--text-dim: #6B6B6B;
|
|
||||||
--text-muted: #4A4A4A;
|
|
||||||
--text: #FFFFFF;
|
|
||||||
|
|
||||||
/* ===== Borders ===== */
|
|
||||||
--border: #333333;
|
|
||||||
--border-color: rgba(255,255,255,0.06);
|
|
||||||
--border-light: #3a3a3a;
|
|
||||||
--border-strong: #4a4a4a;
|
|
||||||
|
|
||||||
/* ===== Accent Colors ===== */
|
|
||||||
--blue: #3366CC;
|
|
||||||
--blue-bg: #1a2744;
|
|
||||||
--green: #00CC66;
|
|
||||||
--green-bg: #1a332a;
|
|
||||||
--yellow: #FFD700;
|
|
||||||
--orange: #D9730D;
|
|
||||||
--red: #E03E3E;
|
|
||||||
--purple: #9B72F0;
|
|
||||||
--pink: #E255A1;
|
|
||||||
--gray: #787774;
|
|
||||||
--brown: #9D6E4A;
|
|
||||||
|
|
||||||
--accent: #2383E2;
|
|
||||||
--accent-hover: #1a6bc0;
|
|
||||||
--danger: #E03E3E;
|
|
||||||
--danger-hover: #C93535;
|
|
||||||
|
|
||||||
/* ===== Typography ===== */
|
|
||||||
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
||||||
--font-mono: 'SF Mono', 'Fira Code', monospace;
|
|
||||||
|
|
||||||
/* ===== Dimensions ===== */
|
|
||||||
--sidebar-width: 240px;
|
|
||||||
--sidebar-collapsed: 0px;
|
|
||||||
--topbar-height: 40px;
|
|
||||||
--radius-sm: 4px;
|
|
||||||
--radius-md: 6px;
|
|
||||||
--radius-lg: 8px;
|
|
||||||
--radius: 8px;
|
|
||||||
|
|
||||||
/* ===== Spacing ===== */
|
|
||||||
--space-xs: 4px;
|
|
||||||
--space-sm: 8px;
|
|
||||||
--space-md: 12px;
|
|
||||||
--space-lg: 16px;
|
|
||||||
--space-xl: 24px;
|
|
||||||
|
|
||||||
/* ===== Transitions ===== */
|
|
||||||
--transition: 0.15s ease;
|
|
||||||
|
|
||||||
/* ===== Toast colors ===== */
|
|
||||||
--toast-success-bg: #0F7B6C;
|
|
||||||
--toast-success-text: #FFFFFF;
|
|
||||||
--toast-error-bg: #E03E3E;
|
|
||||||
--toast-error-text: #FFFFFF;
|
|
||||||
--toast-info-bg: #2383E2;
|
|
||||||
--toast-info-text: #FFFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══════════ LIGHT THEME ═══════════ */
|
|
||||||
[data-theme="light"] {
|
|
||||||
--bg-primary: #FFFFFF;
|
|
||||||
--bg-secondary: #FBFBFA;
|
|
||||||
--bg-sidebar: #F7F6F3;
|
|
||||||
--bg-tertiary: #EDEDEC;
|
|
||||||
--bg-hover: #EFEFEE;
|
|
||||||
--bg-active: #E8E7E4;
|
|
||||||
--bg-modal: #FFFFFF;
|
|
||||||
|
|
||||||
--text-primary: #1A1A1A;
|
|
||||||
--text-secondary: #6B6B6B;
|
|
||||||
--text-dim: #9B9B9B;
|
|
||||||
--text-muted: #B0B0B0;
|
|
||||||
--text: #37352F;
|
|
||||||
|
|
||||||
--border: #E0E0E0;
|
|
||||||
--border-color: rgba(0,0,0,0.06);
|
|
||||||
--border-light: #EDEDEC;
|
|
||||||
--border-strong: #CCCBC8;
|
|
||||||
|
|
||||||
--blue-bg: #E8F0FE;
|
|
||||||
--green-bg: #E6F4EA;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══════════ RESET ADDITIONS ═══════════ */
|
|
||||||
[x-cloak] { display: none !important; }
|
|
||||||
|
|
||||||
/* ═══════════ SKELETON LOADING ═══════════ */
|
|
||||||
.skeleton {
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
animation: skeleton-pulse 1.5s ease-in-out infinite;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes skeleton-pulse {
|
|
||||||
0%, 100% { opacity: 1; }
|
|
||||||
50% { opacity: .5; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Skeleton variants for different contexts */
|
|
||||||
.skeleton-card {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
padding: 20px;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeleton-card .skeleton-line {
|
|
||||||
height: 14px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeleton-card .skeleton-line:last-child { margin-bottom: 0; }
|
|
||||||
|
|
||||||
.skeleton-file {
|
|
||||||
padding: 16px;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
max-height: 70vh;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeleton-line {
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
animation: skeleton-pulse 1.5s ease-in-out infinite;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeleton-table {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeleton-table .skeleton-line {
|
|
||||||
height: 40px;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══════════ TOAST NOTIFICATIONS ═══════════ */
|
|
||||||
.toast-container {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 24px;
|
|
||||||
right: 24px;
|
|
||||||
z-index: 9999;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast {
|
|
||||||
padding: 12px 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
max-width: 400px;
|
|
||||||
box-shadow: 0 8px 24px rgba(0,0,0,.4);
|
|
||||||
pointer-events: auto;
|
|
||||||
animation: toast-slide-in 0.25s ease-out;
|
|
||||||
backdrop-filter: blur(4px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-success {
|
|
||||||
background: var(--toast-success-bg);
|
|
||||||
color: var(--toast-success-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-error {
|
|
||||||
background: var(--toast-error-bg);
|
|
||||||
color: var(--toast-error-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-info {
|
|
||||||
background: var(--toast-info-bg);
|
|
||||||
color: var(--toast-info-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes toast-slide-in {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(12px) scale(0.96);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0) scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══════════ SHARED BUTTONS ═══════════ */
|
|
||||||
.btn {
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 13px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 500;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
background: transparent;
|
|
||||||
color: var(--text-dim);
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
transition: background var(--transition), color var(--transition);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover {
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 13px;
|
|
||||||
cursor: pointer;
|
|
||||||
border: none;
|
|
||||||
font-weight: 500;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
background: var(--accent);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover {
|
|
||||||
background: var(--accent-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-danger {
|
|
||||||
background: rgba(255,80,80,.15);
|
|
||||||
color: var(--danger);
|
|
||||||
border: none;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 13px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 500;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-danger:hover {
|
|
||||||
background: rgba(255,80,80,.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary {
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
color: var(--text);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 13px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 500;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
transition: background var(--transition);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary:hover {
|
|
||||||
background: var(--bg-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-sm {
|
|
||||||
padding: 4px 8px;
|
|
||||||
font-size: 11px;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
background: transparent;
|
|
||||||
color: var(--text-dim);
|
|
||||||
cursor: pointer;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-sm:hover {
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-sm-danger {
|
|
||||||
color: var(--danger);
|
|
||||||
border-color: rgba(255,80,80,.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-new {
|
|
||||||
border: 1px dashed var(--border-strong);
|
|
||||||
background: transparent;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-new:hover {
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══════════ SHARED FORMS ═══════════ */
|
|
||||||
.settings-input {
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 6px;
|
|
||||||
color: var(--text);
|
|
||||||
font-size: 13px;
|
|
||||||
padding: 6px 10px;
|
|
||||||
outline: none;
|
|
||||||
width: 100%;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-input:focus {
|
|
||||||
border-color: var(--accent);
|
|
||||||
box-shadow: 0 0 0 2px rgba(35,131,226,.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-select {
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
color: var(--text);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 6px 10px;
|
|
||||||
font-size: 13px;
|
|
||||||
outline: none;
|
|
||||||
cursor: pointer;
|
|
||||||
min-width: 140px;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-select:focus {
|
|
||||||
border-color: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══════════ SHARED DIALOGS ═══════════ */
|
|
||||||
.dialog-overlay {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(0,0,0,.6);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-box {
|
|
||||||
background: var(--bg-primary);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 18px;
|
|
||||||
padding: 24px;
|
|
||||||
width: 400px;
|
|
||||||
max-width: 90vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-box h3 {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px 12px;
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 8px;
|
|
||||||
color: var(--text);
|
|
||||||
font-size: 14px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
outline: none;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-input:focus {
|
|
||||||
border-color: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══════════ MODAL ═══════════ */
|
|
||||||
.modal-overlay {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(0,0,0,.85);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-box {
|
|
||||||
background: var(--bg-primary);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 24px;
|
|
||||||
min-width: 320px;
|
|
||||||
box-shadow: 0 12px 40px rgba(0,0,0,.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-box h3 {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══════════ DROPDOWN ═══════════ */
|
|
||||||
.dropdown-panel {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
background: var(--bg-primary);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 8px 24px rgba(0,0,0,.5);
|
|
||||||
z-index: 300;
|
|
||||||
min-width: 180px;
|
|
||||||
padding: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-panel.visible {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-option {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--text);
|
|
||||||
cursor: pointer;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-option:hover {
|
|
||||||
background: var(--bg-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══════════ EMPTY STATE ═══════════ */
|
|
||||||
.empty-state {
|
|
||||||
text-align: center;
|
|
||||||
padding: 60px 20px;
|
|
||||||
color: var(--text-dim);
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state h2 {
|
|
||||||
font-size: 18px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══════════ TEXT UTILITIES ═══════════ */
|
|
||||||
.text-dim { color: var(--text-dim); }
|
|
||||||
.text-xs { font-size: 12px; }
|
|
||||||
.text-secondary { color: var(--text-secondary); }
|
|
||||||
|
|
||||||
/* ═══════════ PASSWORD TOGGLE ═══════════ */
|
|
||||||
.pw-wrapper {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pw-toggle {
|
|
||||||
position: absolute;
|
|
||||||
right: 6px;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
padding: 4px;
|
|
||||||
color: var(--text-dim);
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user