A minimal FocusWrite‑inspired editor with local persistence
Welcome to another official UVF IT guide. In the next 45 minutes you’ll spin up a tiny writing app that stores drafts locally, mirroring the core of FocusWrite without any cloud services.
We’ll use Python 3, Flask, and SQLite – all free‑tier, open‑source tools – to deliver a clean UI, a save‑on‑type backend, and a simple focus mode toggle.
pip. The guide skips authentication and rich‑text features – the real FocusWrite includes them.Create a folder, a virtual environment, and install Flask. This gives us a clean, isolated workspace.
mkdir focuswrite_clone && cd focuswrite_clone
python3 -m venv venv
source venv/bin/activate # on Windows use `venv\Scripts\activate`
pip install --upgrade pip
pip install FlaskWe’ll use SQLite with a single drafts table to store title, content, and a timestamp.
init_db.py
import sqlite3
import os
DB_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'drafts.db')
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute('''
CREATE TABLE IF NOT EXISTS drafts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
''')
conn.commit()
conn.close()
print('Database initialized at', DB_PATH)In this step we spin up a tiny Flask server that serves a single HTML page and provides an endpoint for saving drafts. The server will store each save in a local SQLite database, keeping everything on the user's machine. We also expose a static folder so the browser can request the front‑end assets we will add later. This keeps the back‑end minimal yet functional for a local‑first writing app.
app.py
import os
from flask import Flask, request, send_from_directory, jsonify
import sqlite3
app = Flask(__name__, static_folder='static')
DB_PATH = 'drafts.db'
# Ensure the SQLite DB and table exist
if not os.path.exists(DB_PATH):
conn = sqlite3.connect(DB_PATH)
conn.execute('CREATE TABLE drafts (id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)')
conn.commit()
conn.close()
@app.route('/')
def index():
# Serve the main editor page
return send_from_directory(app.static_folder, 'index.html')
@app.route('/save', methods=['POST'])
def save():
data = request.get_json()
content = data.get('content', '')
conn = sqlite3.connect(DB_PATH)
conn.execute('INSERT INTO drafts (content) VALUES (?)', (content,))
conn.commit()
conn.close()
return jsonify({'status': 'saved'}), 200
if __name__ == '__main__':
app.run(debug=True)Now we create the HTML UI that the Flask server will deliver. It contains a large textarea for writing and a button that toggles a "focus mode" class, which hides the button and any surrounding chrome. JavaScript listens for input events, debounces them, and POSTs the current content to the /save endpoint so every keystroke is persisted locally.
static/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Local‑First Writer</title>
<style>
body {font-family: Arial, sans-serif; margin:0; padding:0; height:100vh; display:flex; flex-direction:column;}
#editor {flex:1; width:100%; padding:1rem; box-sizing:border-box; font-size:1.2rem;}
.focus-mode #focusBtn {display:none;}
.focus-mode textarea {background:#fdfdfd;}
</style>
</head>
<body>
<button id="focusBtn">Focus Mode</button>
<textarea id="editor" placeholder="Start typing your masterpiece…"></textarea>
<script>
const editor = document.getElementById('editor');
const btn = document.getElementById('focusBtn');
btn.addEventListener('click', () => document.body.classList.toggle('focus-mode'));
let timeout;
editor.addEventListener('input', () => {
clearTimeout(timeout);
timeout = setTimeout(() => {
fetch('/save', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({content: editor.value})
});
}, 300);
});
</script>
</body>
</html>With the back‑end and front‑end in place, start the Flask development server and open the app in a browser. Begin typing; the JavaScript will debounce and POST each change, and the server will insert a new row into the SQLite file. You can confirm persistence by checking the drafts.db file size or querying the table directly.
python app.py| Check | Command / Why it matters |
|---|---|
| Server starts without errors | python app.py shows *Running on http://127.0.0.1:5000/* |
| Browser loads editor UI | Open http://127.0.0.1:5000/ and see the textarea |
| Typing creates a row in SQLite | After typing, sqlite3 drafts.db "SELECT COUNT(*) FROM drafts;" returns a number >0 |
Add a list view to load previous drafts, implement markdown preview, or sync the DB to a cloud folder for true multi‑device access. Happy hacking!