Initial commit.
This commit is contained in:
6
.env.example
Normal file
6
.env.example
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Copy to .env and adjust
|
||||||
|
KB_VAULT_PATH=/absolute/path/to/your/vault
|
||||||
|
# SECRET_KEY is used by Flask for sessions/CSRF
|
||||||
|
SECRET_KEY=change-me
|
||||||
|
# Enable Flask debug reloader if desired
|
||||||
|
FLASK_DEBUG=1
|
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.pyo
|
||||||
|
*.egg-info/
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
|
||||||
|
# Flask
|
||||||
|
instance/
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Node / Tailwind
|
||||||
|
node_modules/
|
||||||
|
app/static/css/app.css
|
||||||
|
app/static/css/app.css.map
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
79
README.md
Normal file
79
README.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# PKM — Milestone 0 (Project Scaffolding)
|
||||||
|
|
||||||
|
Minimal Flask app with Tailwind + DaisyUI and a welcome page that reads the vault path from configuration.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
- Python 3.9+
|
||||||
|
- Node.js 18+ (for Tailwind CLI)
|
||||||
|
- SQLite (system-provided; FTS5 required later, not needed in M0)
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### 1) Python environment
|
||||||
|
```bash
|
||||||
|
python -m venv .venv
|
||||||
|
# macOS/Linux
|
||||||
|
source .venv/bin/activate
|
||||||
|
# Windows PowerShell
|
||||||
|
# .venv\\Scripts\\Activate.ps1
|
||||||
|
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Optionally install as a package:
|
||||||
|
```bash
|
||||||
|
pip install -e .
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2) Node dependencies and CSS build
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
# One-time build
|
||||||
|
npm run build:css
|
||||||
|
# Or during development (in a separate terminal), watch for changes:
|
||||||
|
npm run watch:css
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3) Configure vault path
|
||||||
|
Create a `.env` from the example and set your vault directory:
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# edit .env and set KB_VAULT_PATH
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, pass the vault path via the CLI when starting the server.
|
||||||
|
|
||||||
|
## Run the app
|
||||||
|
|
||||||
|
Option A — via Flask CLI (loads `create_app` automatically):
|
||||||
|
```bash
|
||||||
|
# Ensure .env is present or set KB_VAULT_PATH in env
|
||||||
|
flask --app app run --debug
|
||||||
|
```
|
||||||
|
|
||||||
|
Option B — via provided CLI:
|
||||||
|
```bash
|
||||||
|
# Using .env
|
||||||
|
python cli.py run
|
||||||
|
# Or pass vault explicitly
|
||||||
|
python cli.py run --vault /path/to/vault
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open:
|
||||||
|
```
|
||||||
|
http://127.0.0.1:5000/
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see:
|
||||||
|
- A welcome card with your configured vault path (or a warning if not set).
|
||||||
|
- Tailwind + DaisyUI styles active.
|
||||||
|
- A theme selector (light/dark/cupcake) in the navbar.
|
||||||
|
|
||||||
|
## Development tips
|
||||||
|
- Run `npm run watch:css` while you work on templates for live CSS rebuilds.
|
||||||
|
- If you rename template paths, update `tailwind.config.cjs` content globs.
|
||||||
|
- Keep `app/static/css/app.css` out of git (it’s built output).
|
||||||
|
|
||||||
|
## Next steps (Milestone 1+)
|
||||||
|
- Implement note model, vault FS access, and basic CRUD.
|
||||||
|
- Add markdown rendering, sanitization, and set up the SQLite schema for indexing.
|
27
app/__init__.py
Normal file
27
app/__init__.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from flask import Flask
|
||||||
|
from .config import load_config
|
||||||
|
|
||||||
|
|
||||||
|
def create_app(config_override=None) -> Flask:
|
||||||
|
app = Flask(
|
||||||
|
__name__,
|
||||||
|
static_folder="static",
|
||||||
|
template_folder="templates",
|
||||||
|
)
|
||||||
|
|
||||||
|
cfg = config_override or load_config()
|
||||||
|
# Map to Flask app.config for easy access in templates/routes
|
||||||
|
app.config["KB_VAULT_PATH"] = cfg.VAULT_PATH
|
||||||
|
app.config["SECRET_KEY"] = cfg.SECRET_KEY
|
||||||
|
app.config["DEBUG"] = cfg.DEBUG
|
||||||
|
|
||||||
|
# Register blueprints
|
||||||
|
from .routes.home import bp as home_bp
|
||||||
|
app.register_blueprint(home_bp)
|
||||||
|
|
||||||
|
# Simple health endpoint
|
||||||
|
@app.get("/healthz")
|
||||||
|
def healthz():
|
||||||
|
return {"status": "ok", "vault": app.config.get("KB_VAULT_PATH")}, 200
|
||||||
|
|
||||||
|
return app
|
22
app/config.py
Normal file
22
app/config.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import os
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AppConfig:
|
||||||
|
DEBUG: bool
|
||||||
|
SECRET_KEY: str
|
||||||
|
VAULT_PATH: str | None
|
||||||
|
|
||||||
|
|
||||||
|
def load_config(vault_override: str | None = None) -> AppConfig:
|
||||||
|
# Load .env if present
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
debug = os.getenv("FLASK_DEBUG", "0") in ("1", "true", "True")
|
||||||
|
secret = os.getenv("SECRET_KEY", "dev-secret-change-me")
|
||||||
|
vault_env = os.getenv("KB_VAULT_PATH")
|
||||||
|
|
||||||
|
vault = vault_override or vault_env
|
||||||
|
return AppConfig(DEBUG=debug, SECRET_KEY=secret, VAULT_PATH=vault)
|
1
app/routes/__init__.py
Normal file
1
app/routes/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Package marker for routes
|
10
app/routes/home.py
Normal file
10
app/routes/home.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from flask import Blueprint, current_app, render_template
|
||||||
|
|
||||||
|
bp = Blueprint("home", __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.get("/")
|
||||||
|
def home():
|
||||||
|
vault_path = current_app.config.get("KB_VAULT_PATH")
|
||||||
|
has_vault = bool(vault_path)
|
||||||
|
return render_template("home.html", vault_path=vault_path, has_vault=has_vault)
|
5
app/static/css/input.css
Normal file
5
app/static/css/input.css
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* You can add custom CSS here if needed */
|
18
app/static/js/app.js
Normal file
18
app/static/js/app.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
(function themeInit() {
|
||||||
|
try {
|
||||||
|
const html = document.documentElement;
|
||||||
|
const select = document.getElementById("theme-select");
|
||||||
|
const saved = localStorage.getItem("theme") || "light";
|
||||||
|
html.setAttribute("data-theme", saved);
|
||||||
|
if (select) {
|
||||||
|
select.value = saved;
|
||||||
|
select.addEventListener("change", () => {
|
||||||
|
const value = select.value;
|
||||||
|
html.setAttribute("data-theme", value);
|
||||||
|
localStorage.setItem("theme", value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
})();
|
35
app/templates/base.html
Normal file
35
app/templates/base.html
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" data-theme="light">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
<title>{% block title %}PKM{% endblock %}</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}" />
|
||||||
|
<script defer src="{{ url_for('static', filename='js/app.js') }}"></script>
|
||||||
|
</head>
|
||||||
|
<body class="min-h-screen bg-base-200">
|
||||||
|
<div class="navbar bg-base-100 shadow">
|
||||||
|
<div class="flex-1">
|
||||||
|
<a href="{{ url_for('home.home') }}" class="btn btn-ghost text-xl">PKM</a>
|
||||||
|
</div>
|
||||||
|
<div class="flex-none gap-2">
|
||||||
|
<label class="label mr-2"><span class="label-text">Theme</span></label>
|
||||||
|
<select id="theme-select" class="select select-sm select-bordered">
|
||||||
|
<option>light</option>
|
||||||
|
<option>dark</option>
|
||||||
|
<option>cupcake</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<main class="container mx-auto p-4">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="footer items-center p-4 bg-base-100 text-base-content border-t">
|
||||||
|
<aside class="grid-flow-col items-center">
|
||||||
|
<p>PKM — Flask + Tailwind + DaisyUI</p>
|
||||||
|
</aside>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
26
app/templates/home.html
Normal file
26
app/templates/home.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}PKM — Welcome{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="card bg-base-100 shadow">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title">Welcome</h2>
|
||||||
|
{% if has_vault %}
|
||||||
|
<p>Your vault path is configured:</p>
|
||||||
|
<code class="kbd kbd-sm">{{ vault_path }}</code>
|
||||||
|
<p class="mt-2">You're ready to proceed with indexing and note features in the next milestones.</p>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<span>No vault path configured.</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2">
|
||||||
|
Set KB_VAULT_PATH in your environment or run via the CLI with
|
||||||
|
<span class="kbd kbd-sm">pkm run --vault /path/to/vault</span>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
<div class="divider"></div>
|
||||||
|
<p class="text-sm opacity-70">
|
||||||
|
Tailwind + DaisyUI is active. Try switching theme in the navbar.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
32
cli.py
Normal file
32
cli.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import os
|
||||||
|
import click
|
||||||
|
from app import create_app
|
||||||
|
from app.config import load_config
|
||||||
|
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
def main():
|
||||||
|
"""PKM CLI."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@main.command("run")
|
||||||
|
@click.option("--vault", "vault_path", type=click.Path(file_okay=False, dir_okay=True, path_type=str), help="Path to the vault directory")
|
||||||
|
@click.option("--host", default="127.0.0.1", show_default=True, help="Host to bind")
|
||||||
|
@click.option("--port", default=5000, show_default=True, help="Port to bind")
|
||||||
|
@click.option("--debug/--no-debug", default=True, show_default=True, help="Enable/disable debug mode")
|
||||||
|
def runserver(vault_path: str | None, host: str, port: int, debug: bool):
|
||||||
|
"""Run the development server."""
|
||||||
|
# Prefer CLI vault over env
|
||||||
|
cfg = load_config(vault_override=vault_path)
|
||||||
|
# Mirror into environment for consistency if needed
|
||||||
|
if cfg.VAULT_PATH:
|
||||||
|
os.environ["KB_VAULT_PATH"] = cfg.VAULT_PATH
|
||||||
|
os.environ["FLASK_DEBUG"] = "1" if debug else "0"
|
||||||
|
|
||||||
|
app = create_app(cfg)
|
||||||
|
app.run(host=host, port=port, debug=debug)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
140
docs/BACKLOG.md
Normal file
140
docs/BACKLOG.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# Personal Knowledge Manager (PKM) — Concrete Backlog
|
||||||
|
|
||||||
|
Scope for v1 (agreed)
|
||||||
|
- Single vault folder on disk (user-provided path).
|
||||||
|
- Files are Markdown with YAML front matter; Obsidian-compatible (front matter, [[WikiLinks]], relative links, optional inline #tags).
|
||||||
|
- Flask backend; Tailwind + DaisyUI frontend; server-rendered pages with light JS.
|
||||||
|
- CodeMirror 6 editor from the start.
|
||||||
|
- Search/index via SQLite FTS5 stored under .kb/index.sqlite within the vault.
|
||||||
|
- No Git versioning and no graph view in v1 (reserved for later).
|
||||||
|
|
||||||
|
Non-goals for v1
|
||||||
|
- Multi-user auth, sync, remote deployment concerns.
|
||||||
|
- Git auto-commit/history UI.
|
||||||
|
- Graph visualization.
|
||||||
|
- Plugin system.
|
||||||
|
|
||||||
|
Definitions and conventions
|
||||||
|
- Note file naming: slugified from title, e.g., `notes/event-sourcing-overview.md`.
|
||||||
|
- Front matter includes: id (UUID), title, created, updated; optional aliases, tags, status, summary, template.
|
||||||
|
- Backlinks stored in index (not written into files).
|
||||||
|
- [[WikiLinks]] resolve by title/alias/slug; Markdown links preserved and resolved relatively.
|
||||||
|
- Vault internals live under `<vault>/.kb/` (SQLite index, config, cache).
|
||||||
|
|
||||||
|
Milestone 0 — Project scaffolding
|
||||||
|
- Tasks
|
||||||
|
- Initialize Flask app scaffolding and configuration.
|
||||||
|
- Tailwind + DaisyUI build pipeline via Tailwind CLI.
|
||||||
|
- Base Jinja templates and minimal layout with DaisyUI theme.
|
||||||
|
- Environment/config management (vault path, debug).
|
||||||
|
- Acceptance criteria
|
||||||
|
- App starts with `flask run` (or a `python -m` entrypoint).
|
||||||
|
- Tailwind builds and serves CSS; DaisyUI classes render correctly.
|
||||||
|
- Config reads vault path from env/config and shows a welcome page.
|
||||||
|
|
||||||
|
Milestone 1 — Vault and note model
|
||||||
|
- Tasks
|
||||||
|
- Filesystem repository for notes (read/write Markdown + YAML front matter).
|
||||||
|
- Create new note with required metadata; slugify filenames from `title`.
|
||||||
|
- Validate and normalize metadata (UUID, ISO timestamps).
|
||||||
|
- Resolve note identity by `id` independent of filename changes.
|
||||||
|
- Acceptance criteria
|
||||||
|
- Can create a note; it is saved to disk with valid front matter.
|
||||||
|
- Can read an existing note and get parsed metadata + body.
|
||||||
|
- Tests for slug generation and metadata normalization.
|
||||||
|
|
||||||
|
Milestone 2 — Rendering and viewing
|
||||||
|
- Tasks
|
||||||
|
- Markdown rendering via markdown-it-py with plugins (tables, task lists, footnotes, admonitions).
|
||||||
|
- HTML sanitization (bleach) with a safe allowlist.
|
||||||
|
- Obsidian-compatible rendering: [[WikiLinks]] clickable, resolve to note routes; render relative links and attachments; optionally parse inline #tags.
|
||||||
|
- Basic note view page: title, metadata pills (tags/status), content, attachments preview.
|
||||||
|
- Acceptance criteria
|
||||||
|
- Visiting a note route renders sanitized HTML.
|
||||||
|
- [[WikiLinks]] and relative links resolve; unknown links are indicated.
|
||||||
|
- Attachment links/images load from the vault.
|
||||||
|
|
||||||
|
Milestone 3 — Editor with CodeMirror 6
|
||||||
|
- Tasks
|
||||||
|
- Note edit page using CodeMirror 6 (Markdown extensions, basic shortcuts).
|
||||||
|
- Front matter editor (simple form for title/tags/status; syncs with file).
|
||||||
|
- Save flow: update `updated`; create if new; optimistic UI.
|
||||||
|
- Drag-and-drop/paste image => save under `attachments/` and insert relative path.
|
||||||
|
- Acceptance criteria
|
||||||
|
- Can create, edit, and save notes from the browser.
|
||||||
|
- Pasting/dragging an image adds a file and inserts a link.
|
||||||
|
- Title change updates slug/filename on save (see Milestone 6 for link rewrites).
|
||||||
|
|
||||||
|
Milestone 4 — Indexing and search (SQLite FTS5)
|
||||||
|
- Tasks
|
||||||
|
- SQLite schema under `<vault>/.kb/index.sqlite`.
|
||||||
|
- Initial indexing pass: ingest existing notes (title, body, tags, path, id).
|
||||||
|
- File watching (watchdog) for incremental index updates on add/edit/delete/rename.
|
||||||
|
- Search API with filters (q=, tag=, status=, path=) and boosting (title > tags > body).
|
||||||
|
- Search UI with instant results (debounced) and simple filters.
|
||||||
|
- Acceptance criteria
|
||||||
|
- Searching returns relevant notes quickly.
|
||||||
|
- Edits trigger index updates without manual rebuild.
|
||||||
|
- Tag list and per-tag results available.
|
||||||
|
|
||||||
|
Milestone 5 — Links and backlinks
|
||||||
|
- Tasks
|
||||||
|
- Outbound link extraction (Markdown links + [[WikiLinks]]); store in index.
|
||||||
|
- Backlinks computation and storage; display backlinks in note view.
|
||||||
|
- Handle aliases/title collisions deterministically (id/slug precedence).
|
||||||
|
- Acceptance criteria
|
||||||
|
- Each note shows backlinks (with context snippet if available).
|
||||||
|
- Creating a link updates backlinks after save.
|
||||||
|
|
||||||
|
Milestone 6 — Rename/move and link maintenance
|
||||||
|
- Tasks
|
||||||
|
- Rename/move note (on title or folder change): update slug/path on disk.
|
||||||
|
- Inbound link rewrite: update Markdown files that link to the old path/slug.
|
||||||
|
- Safe operations (backup to temp, atomic writes) and dry-run option (internal).
|
||||||
|
- Acceptance criteria
|
||||||
|
- Renaming a note updates its filename and maintains working links in other notes.
|
||||||
|
- Index reflects new paths; no broken links in backlinking notes.
|
||||||
|
|
||||||
|
Milestone 7 — UX polish
|
||||||
|
- Tasks
|
||||||
|
- Command palette / quick open (fuzzy search) and keyboard shortcuts.
|
||||||
|
- DaisyUI theme switch (light/dark/system) and remember preference.
|
||||||
|
- Empty states, loading states, toasts for save operations.
|
||||||
|
- Acceptance criteria
|
||||||
|
- Keyboard-driven navigation works end-to-end.
|
||||||
|
- Theme switch persists and is applied.
|
||||||
|
|
||||||
|
Milestone 8 — Packaging and quality
|
||||||
|
- Tasks
|
||||||
|
- CLI launcher: `pkm run --vault /path/to/vault` (or `python -m pkm`).
|
||||||
|
- Basic error pages and diagnostics (e.g., FTS5 availability, permission errors).
|
||||||
|
- Unit tests for core services (parsing, indexing, link rewrite).
|
||||||
|
- Minimal Dockerfile (optional) for local use with volume mount.
|
||||||
|
- Acceptance criteria
|
||||||
|
- One-command startup with vault path.
|
||||||
|
- Tests for core logic pass in CI (if configured).
|
||||||
|
|
||||||
|
Deferred items (post-v1)
|
||||||
|
- Git auto-commit/history and diffs.
|
||||||
|
- Graph view.
|
||||||
|
- Auth for remote hosting.
|
||||||
|
- Templates/daily notes generator; import/export UI; plugin hooks.
|
||||||
|
|
||||||
|
Risks and mitigations
|
||||||
|
- FTS5 availability: detect and warn at startup; fall back to basic search read-path if unavailable (optional).
|
||||||
|
- Large vault performance: chunked indexing, debounced file watcher, background tasks with status UI.
|
||||||
|
- Link rewrite safety: transactional writes with backups; verify before replace.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[Vault] -->|Parse| B[Note Model]
|
||||||
|
B --> C[Render Markdown]
|
||||||
|
B --> D[Index FTS5]
|
||||||
|
D --> E[Search API/UI]
|
||||||
|
B --> F[Link Extract]
|
||||||
|
F --> G[Backlinks]
|
||||||
|
B --> H[Editor (CM6)]
|
||||||
|
H -->|Save| A
|
||||||
|
H -->|Save| D
|
||||||
|
H -->|Save| F
|
||||||
|
```
|
125
docs/STRUCTURE.md
Normal file
125
docs/STRUCTURE.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
# Initial Directory Structure
|
||||||
|
|
||||||
|
This is the proposed repository structure (application code), plus expected vault layout on disk.
|
||||||
|
|
||||||
|
Repository tree
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├─ app/
|
||||||
|
│ ├─ __init__.py # Flask app factory and Jinja/Tailwind integration
|
||||||
|
│ ├─ config.py # Config: vault path, debug flags, security, etc.
|
||||||
|
│ ├─ routes/
|
||||||
|
│ │ ├─ notes.py # List/view/create/edit/delete notes; rename/move
|
||||||
|
│ │ ├─ search.py # Search endpoints (JSON + HTML)
|
||||||
|
│ │ ├─ tags.py # Tags listing and per-tag view
|
||||||
|
│ │ ├─ attachments.py # Upload/serve attachments
|
||||||
|
│ │ └─ api.py # JSON APIs (if split from HTML routes)
|
||||||
|
│ ├─ services/
|
||||||
|
│ │ ├─ vault.py # Vault path mgmt, discovery, .kb paths
|
||||||
|
│ │ ├─ notes_fs.py # Read/write Markdown + YAML; slugging; UUID mgmt
|
||||||
|
│ │ ├─ renderer.py # Markdown-it-py + bleach sanitization
|
||||||
|
│ │ ├─ indexer.py # SQLite FTS5 schema + indexing + queries
|
||||||
|
│ │ ├─ links.py # Extract Markdown/[[WikiLinks]]; resolve; backlinks
|
||||||
|
│ │ └─ watcher.py # watchdog observers to update index on changes
|
||||||
|
│ ├─ utils/
|
||||||
|
│ │ ├─ time.py # ISO timestamp helpers
|
||||||
|
│ │ ├─ slugs.py # Slugify utilities
|
||||||
|
│ │ └─ paths.py # Safe atomic writes, backups, path utils
|
||||||
|
│ ├─ templates/
|
||||||
|
│ │ ├─ _partials/
|
||||||
|
│ │ │ ├─ navbar.html
|
||||||
|
│ │ │ ├─ note_meta.html # Tags/status/aliases pills
|
||||||
|
│ │ │ └─ search_box.html
|
||||||
|
│ │ ├─ base.html # DaisyUI layout; theme switch; CSS/JS includes
|
||||||
|
│ │ ├─ home.html # Recent notes; quick search
|
||||||
|
│ │ ├─ notes/
|
||||||
|
│ │ │ ├─ list.html
|
||||||
|
│ │ │ ├─ view.html # Content + backlinks + outbound links
|
||||||
|
│ │ │ └─ edit.html # CodeMirror editor + metadata form
|
||||||
|
│ │ └─ search/
|
||||||
|
│ │ └─ results.html
|
||||||
|
│ ├─ static/
|
||||||
|
│ │ ├─ css/
|
||||||
|
│ │ │ ├─ input.css # @tailwind base; @tailwind components; @tailwind utilities;
|
||||||
|
│ │ │ └─ app.css # Built CSS (gitignored if desired)
|
||||||
|
│ │ ├─ js/
|
||||||
|
│ │ │ ├─ editor.js # CodeMirror 6 init (ESM); drag-drop/paste handler
|
||||||
|
│ │ │ └─ app.js # Small client-side helpers (palette, shortcuts)
|
||||||
|
│ │ └─ imgs/
|
||||||
|
│ │ └─ logo.svg
|
||||||
|
│ └─ schema.sql # SQLite schema incl. FTS5 and backlinks tables
|
||||||
|
├─ cli.py # CLI entrypoint: `pkm run --vault <path>`
|
||||||
|
├─ pyproject.toml # Python project + dependencies (Flask, markdown-it-py, bleach, watchdog, pyyaml)
|
||||||
|
├─ requirements.txt # (optional alternative to pyproject)
|
||||||
|
├─ tailwind.config.cjs # Tailwind + DaisyUI plugin config
|
||||||
|
├─ postcss.config.cjs # (optional, if using PostCSS plugins explicitly)
|
||||||
|
├─ package.json # Dev deps: tailwindcss, daisyui; scripts to build/watch CSS
|
||||||
|
├─ .gitignore
|
||||||
|
├─ .env.example # KB_VAULT_PATH=...
|
||||||
|
└─ README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
SQLite schema outline (`app/schema.sql`)
|
||||||
|
```sql
|
||||||
|
-- Notes catalog (lightweight metadata separate from content)
|
||||||
|
CREATE TABLE IF NOT EXISTS notes (
|
||||||
|
id TEXT PRIMARY KEY, -- UUID
|
||||||
|
path TEXT NOT NULL, -- relative to vault root, e.g., notes/foo/bar.md
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
created TEXT NOT NULL, -- ISO UTC
|
||||||
|
updated TEXT NOT NULL, -- ISO UTC
|
||||||
|
tags TEXT, -- JSON-encoded array or comma-separated
|
||||||
|
status TEXT,
|
||||||
|
aliases TEXT -- JSON-encoded array
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Backlinks (resolved by id)
|
||||||
|
CREATE TABLE IF NOT EXISTS links (
|
||||||
|
src_id TEXT NOT NULL,
|
||||||
|
dst_id TEXT NOT NULL,
|
||||||
|
src_anchor TEXT, -- optional: section/heading/context
|
||||||
|
PRIMARY KEY (src_id, dst_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- FTS5 virtual table for full-text search
|
||||||
|
CREATE VIRTUAL TABLE IF NOT EXISTS notes_fts
|
||||||
|
USING fts5(
|
||||||
|
title, body, tags,
|
||||||
|
content='',
|
||||||
|
tokenize='porter' -- or unicode61 with custom config
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Content-to-FTS sync tables (manual sync via triggers or code)
|
||||||
|
CREATE TABLE IF NOT EXISTS notes_content (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
body TEXT NOT NULL
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Vault layout on disk (user-provided path; created/validated at first run)
|
||||||
|
```
|
||||||
|
<vault>/
|
||||||
|
├─ notes/ # Markdown notes (nested folders allowed)
|
||||||
|
├─ attachments/ # Images/PDFs and other assets
|
||||||
|
└─ .kb/
|
||||||
|
├─ index.sqlite # FTS5 index + backlinks + metadata cache
|
||||||
|
├─ config.yml # App config/preferences (optional for v1)
|
||||||
|
└─ cache/ # Thumbnails, temp files (optional)
|
||||||
|
```
|
||||||
|
|
||||||
|
Dependencies (initial)
|
||||||
|
- Python: Flask, Jinja2, markdown-it-py, bleach, PyYAML (or python-frontmatter), watchdog, click (for CLI).
|
||||||
|
- System: SQLite with FTS5 enabled (commonly available).
|
||||||
|
- Frontend: Tailwind CSS, DaisyUI (Tailwind plugin), CodeMirror 6 via ESM imports (from CDN) or npm if bundling later.
|
||||||
|
|
||||||
|
Scripts (suggested)
|
||||||
|
- package.json
|
||||||
|
- build: `tailwindcss -c tailwind.config.cjs -i ./app/static/css/input.css -o ./app/static/css/app.css --minify`
|
||||||
|
- watch: `tailwindcss -c tailwind.config.cjs -i ./app/static/css/input.css -o ./app/static/css/app.css --watch`
|
||||||
|
- CLI
|
||||||
|
- `python cli.py run --vault /path/to/vault` (wraps Flask app with vault path)
|
||||||
|
|
||||||
|
Notes
|
||||||
|
- Keep server-rendered pages; use minimal JS for editor and quick search.
|
||||||
|
- Sanitize rendered HTML; set a Content Security Policy where possible.
|
||||||
|
- For Obsidian compatibility, prefer not to modify file structure beyond `.kb` internals.
|
1531
package-lock.json
generated
Normal file
1531
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
package.json
Normal file
14
package.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "pkm-frontend",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Tailwind + DaisyUI build for PKM",
|
||||||
|
"scripts": {
|
||||||
|
"build:css": "tailwindcss -c tailwind.config.cjs -i ./app/static/css/input.css -o ./app/static/css/app.css --minify",
|
||||||
|
"watch:css": "tailwindcss -c tailwind.config.cjs -i ./app/static/css/input.css -o ./app/static/css/app.css --watch"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"daisyui": "^4.12.10",
|
||||||
|
"tailwindcss": "^3.4.10"
|
||||||
|
}
|
||||||
|
}
|
21
pyproject.toml
Normal file
21
pyproject.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["hatchling>=1.18.0"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "pkm"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Personal Knowledge Manager (Flask + Tailwind + DaisyUI)"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.9"
|
||||||
|
dependencies = [
|
||||||
|
"Flask>=3.0",
|
||||||
|
"python-dotenv>=1.0",
|
||||||
|
"click>=8.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = []
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
pkm = "cli:main"
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Flask>=3.0
|
||||||
|
python-dotenv>=1.0
|
||||||
|
click>=8.1
|
14
tailwind.config.cjs
Normal file
14
tailwind.config.cjs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
"./app/templates/**/*.html",
|
||||||
|
"./app/static/js/**/*.js",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [require("daisyui")],
|
||||||
|
daisyui: {
|
||||||
|
themes: ["light", "dark", "cupcake"],
|
||||||
|
},
|
||||||
|
};
|
Reference in New Issue
Block a user