labels: per-label color
Give labels a color (migration 0011 adds labels.color, server_default 'default' so existing labels keep the neutral chip). The PATCH endpoint now updates name and/or color; note serialization carries each label's color. Frontend: a swatch picker per label in the Edit-labels modal, colored chips on cards + in the editor (LABEL_CHIP_CLASSES), and a color dot on each sidebar label. Reuses the note color vocabulary. (Graph node coloring rides this in the graph-liveliness task.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
+25
-11
@@ -12,8 +12,15 @@ from .models.label import Label
|
||||
bp = Blueprint("labels", __name__, url_prefix="/api/labels")
|
||||
|
||||
|
||||
LABEL_COLORS = {"default", "red", "orange", "yellow", "green", "teal", "blue", "purple", "pink", "gray"}
|
||||
|
||||
|
||||
def _normalize_label_color(color: object) -> str:
|
||||
return color if color in LABEL_COLORS else "default"
|
||||
|
||||
|
||||
def _serialize_label(label: Label) -> dict:
|
||||
return {"id": str(label.id), "name": label.name}
|
||||
return {"id": str(label.id), "name": label.name, "color": label.color}
|
||||
|
||||
|
||||
async def _get_owned_label(db, label_id: str) -> Label | None:
|
||||
@@ -44,7 +51,7 @@ async def create_label():
|
||||
existing = await db.scalar(select(Label).where(Label.owner_id == g.user_id, Label.name == name))
|
||||
if existing is not None:
|
||||
return jsonify(_serialize_label(existing)), 200
|
||||
label = Label(owner_id=g.user_id, name=name)
|
||||
label = Label(owner_id=g.user_id, name=name, color=_normalize_label_color(data.get("color")))
|
||||
db.add(label)
|
||||
await db.commit()
|
||||
await db.refresh(label)
|
||||
@@ -53,21 +60,28 @@ async def create_label():
|
||||
|
||||
@bp.patch("/<label_id>")
|
||||
@login_required
|
||||
async def rename_label(label_id: str):
|
||||
async def update_label(label_id: str):
|
||||
data = await request.get_json(silent=True) or {}
|
||||
name = (data.get("name") or "").strip()
|
||||
if not name:
|
||||
has_name = "name" in data
|
||||
has_color = "color" in data
|
||||
if not has_name and not has_color:
|
||||
return jsonify({"error": "nothing to update"}), 400
|
||||
name = (data.get("name") or "").strip() if has_name else None
|
||||
if has_name and not name:
|
||||
return jsonify({"error": "label name is required"}), 400
|
||||
async with session_scope() as db:
|
||||
label = await _get_owned_label(db, label_id)
|
||||
if label is None:
|
||||
return jsonify({"error": "not found"}), 404
|
||||
clash = await db.scalar(
|
||||
select(Label).where(Label.owner_id == g.user_id, Label.name == name, Label.id != label.id)
|
||||
)
|
||||
if clash is not None:
|
||||
return jsonify({"error": "a label with that name already exists"}), 409
|
||||
label.name = name
|
||||
if has_name:
|
||||
clash = await db.scalar(
|
||||
select(Label).where(Label.owner_id == g.user_id, Label.name == name, Label.id != label.id)
|
||||
)
|
||||
if clash is not None:
|
||||
return jsonify({"error": "a label with that name already exists"}), 409
|
||||
label.name = name
|
||||
if has_color:
|
||||
label.color = _normalize_label_color(data.get("color"))
|
||||
await db.commit()
|
||||
await db.refresh(label)
|
||||
return jsonify(_serialize_label(label))
|
||||
|
||||
Reference in New Issue
Block a user