Stage 1: app skeleton — Electron window, sidebar/editor layout, light/dark CSS, esbuild build, typecheck
This commit is contained in:
commit
6fac0d013b
12 changed files with 2488 additions and 0 deletions
35
src/main/index.ts
Normal file
35
src/main/index.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { app, BrowserWindow } from "electron";
|
||||
import path from "path";
|
||||
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1280,
|
||||
height: 780,
|
||||
minWidth: 960,
|
||||
minHeight: 640,
|
||||
title: "Folio",
|
||||
webPreferences: {
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
preload: path.join(__dirname, "preload.js"),
|
||||
},
|
||||
});
|
||||
|
||||
mainWindow.loadFile(path.join(__dirname, "index.html"));
|
||||
mainWindow.webContents.on("did-finish-load", () =>
|
||||
console.log("[folio] renderer loaded")
|
||||
);
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
||||
});
|
||||
});
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") app.quit();
|
||||
});
|
||||
3
src/main/preload.ts
Normal file
3
src/main/preload.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
window.addEventListener("DOMContentLoaded", () => {
|
||||
console.log("[folio] dom ready");
|
||||
});
|
||||
160
src/renderer/index.css
Normal file
160
src/renderer/index.css
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/* System-font stack only — no remote fonts. */
|
||||
:root {
|
||||
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
--font-serif: Georgia, "Times New Roman", serif;
|
||||
--radius: 8px;
|
||||
--gap: 16px;
|
||||
|
||||
/* Light theme */
|
||||
--color-bg: #ffffff;
|
||||
--color-panel: #f7f7f8;
|
||||
--color-border: #e2e2e7;
|
||||
--color-text: #1a1a1d;
|
||||
--color-muted: #6b6b76;
|
||||
--color-accent: #2563eb;
|
||||
--color-accent-hover: #1d4ed8;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-bg: #0f0f12;
|
||||
--color-panel: #18181e;
|
||||
--color-border: #2a2a32;
|
||||
--color-text: #e6e6ec;
|
||||
--color-muted: #9a9aa6;
|
||||
--color-accent: #4a94f0;
|
||||
--color-accent-hover: #6ab0ff;
|
||||
}
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
font-family: var(--font-sans);
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
#root {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
flex: 0 0 280px;
|
||||
min-width: 240px;
|
||||
max-width: 360px;
|
||||
background: var(--color-panel);
|
||||
border-right: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(--gap);
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding-bottom: var(--gap);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.sidebar-title {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.chapter-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-top: var(--gap);
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.chapter-list li {
|
||||
padding: 8px 10px;
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
font-family: var(--font-serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--color-text);
|
||||
transition: background 0.12s ease, color 0.12s ease;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.chapter-list li:hover {
|
||||
background: var(--color-accent);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.chapter-list li.active {
|
||||
background: var(--color-accent);
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
padding-top: var(--gap);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius);
|
||||
padding: 6px 12px;
|
||||
font-size: 0.85rem;
|
||||
font-family: var(--font-sans);
|
||||
cursor: pointer;
|
||||
transition: background 0.12s ease, border-color 0.12s ease;
|
||||
background: transparent;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: var(--color-accent);
|
||||
color: #ffffff;
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
.button-ghost {
|
||||
border: 1px dashed var(--color-border);
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.button-ghost:hover {
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.editor {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--color-bg);
|
||||
padding: var(--gap);
|
||||
}
|
||||
|
||||
.editor-placeholder {
|
||||
max-width: 560px;
|
||||
text-align: center;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.editor-placeholder p {
|
||||
font-size: 1rem;
|
||||
margin: 0 0 6px;
|
||||
}
|
||||
30
src/renderer/index.html
Normal file
30
src/renderer/index.html
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Folio</title>
|
||||
<link rel="stylesheet" href="./index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
<aside class="sidebar" id="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<span class="sidebar-title">Chapters</span>
|
||||
</div>
|
||||
<ul class="chapter-list" id="chapterList"></ul>
|
||||
<div class="sidebar-footer">
|
||||
<button class="button button-ghost" id="newChapterBtn">
|
||||
+ New Chapter
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="editor" id="editor">
|
||||
<div class="editor-placeholder">
|
||||
<p>Select a chapter to begin writing.</p>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<script src="./renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
8
src/renderer/index.ts
Normal file
8
src/renderer/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
const root = document.getElementById("root")!;
|
||||
|
||||
root.addEventListener("dragover", (e) => e.preventDefault());
|
||||
root.addEventListener("drop", (e) => e.preventDefault());
|
||||
|
||||
document.getElementById("newChapterBtn")!.addEventListener("click", () => {
|
||||
alert("Chapter creation coming soon.");
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue