Properties: colours by type, readable keys, lines between

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
sarahandClaude Opus 5.5 committed 2026-10-13 16:35:21 +02:00
1 parent ca37409e37
commit 81ebcd66a1
3 files changed
+101 -14

No files matched your search

+55 -9
View File
@@ -23,6 +23,37 @@ OpenOrClose = re.compile(r"^---\s*$")
PropertyLine = re.compile(r"^([^:\s][^:]*?)\s*:\s*(.*)$")
TaskLine = re.compile(r"^(\s*(?:[-*+]|\d+[.)])\s+)\[( |x|X)\]")
BooleanValue = re.compile(r"^(true|false)$", re.IGNORECASE)
Quoted = re.compile(r"""^(["']).*\1$""")
IntegerValue = re.compile(r"^[+-]?\d+$")
FloatValue = re.compile(r"^[+-]?(\d+\.\d*([eE][+-]?\d+)?|\.\d+([eE][+-]?\d+)?|\d+[eE][+-]?\d+)$")
DateValue = re.compile(r"^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}(:\d{2})?)?$")
def ValueKind(Value: str) -> str:
"""What a value is, for its colour: quotes always make it text ("42" is
text, 42 a number), like YAML. Same rules as Properties.js's ValueKind."""
if Value == "":
return "Empty"
if Quoted.match(Value):
return "String"
if BooleanValue.match(Value):
return "Boolean"
if IntegerValue.match(Value):
return "Integer"
if FloatValue.match(Value):
return "Float"
if DateValue.match(Value):
return "Date"
return "String"
def ShownValue(Value: str) -> str:
"""A value as shown: without the quotes that only said "this is text"."""
return Value[1:-1] if Quoted.match(Value) else Value
def KindColour(Kind: str) -> str:
return {"Integer": Palette.Peach, "Float": Palette.Yellow, "Date": Palette.Blue}.get(Kind, Palette.Green)
# Ticks a box: called with the line (0-based, in the whole note) to flip.
OnToggle = Optional[Callable[[int], None]]
@@ -93,26 +124,41 @@ def _Checkbox(Checked: bool, Line: int, Toggle: OnToggle) -> ft.Checkbox:
)
def _Value(Value: str, FontSize: int, MakeMarkdown: Callable[[str], ft.Control]) -> ft.Control:
"""A value in its kind's colour; one with Markdown in it (a link, bold)
is rendered as Markdown instead."""
Shown = ShownValue(Value)
if re.search(r"[\[*_`~]|https?://", Shown):
return MakeMarkdown(Shown)
return ft.Text(Shown, color=KindColour(ValueKind(Value)), size=FontSize, selectable=True)
def PropertiesView(Found: Parsed, FontSize: int, MakeMarkdown: Callable[[str], ft.Control],
Toggle: OnToggle) -> ft.Control:
"""The panel. MakeMarkdown renders a value (links, bold)."""
"""The panel. MakeMarkdown renders a value with Markdown in it (links, bold)."""
Rows: list[ft.Control] = []
for Item in Found.Properties:
if BooleanValue.match(Item.Value):
Shown: ft.Control = _Checkbox(Item.Value.lower() == "true", Item.Line, Toggle)
elif Item.List:
Shown = ft.Row([ft.Container(ft.Text(Entry, color=Palette.Mauve, size=FontSize - 1), bgcolor=Palette.Surface0,
border_radius=4, padding=ft.Padding.symmetric(horizontal=6, vertical=1))
Shown = ft.Row([ft.Container(ft.Text(ShownValue(Entry), color=KindColour(ValueKind(Entry)), size=FontSize - 1),
bgcolor=Palette.Surface0, border_radius=4,
padding=ft.Padding.symmetric(horizontal=6, vertical=1))
for Entry in Item.List], wrap=True, spacing=4, run_spacing=4)
elif Item.Value:
Shown = MakeMarkdown(Item.Value)
Shown = _Value(Item.Value, FontSize, MakeMarkdown)
else:
Shown = ft.Text("Empty", color=Palette.Overlay0, italic=True, size=FontSize - 1)
Rows.append(ft.Row([
ft.Container(ft.Text(Item.Key, color=Palette.Overlay0, size=FontSize - 1, max_lines=1,
overflow=ft.TextOverflow.ELLIPSIS), expand=3),
ft.Container(Shown, expand=7, alignment=ft.Alignment.CENTER_LEFT),
], spacing=12, vertical_alignment=ft.CrossAxisAlignment.CENTER))
Rows.append(ft.Container(
ft.Row([
ft.Container(ft.Text(Item.Key, color=Palette.Text, size=FontSize - 1, max_lines=1,
overflow=ft.TextOverflow.ELLIPSIS), expand=3),
ft.Container(Shown, expand=7, alignment=ft.Alignment.CENTER_LEFT),
], spacing=12, vertical_alignment=ft.CrossAxisAlignment.CENTER),
# A line between properties, like the web view's.
border=ft.Border.only(top=ft.BorderSide(1, Palette.Surface1)) if Rows else None,
padding=ft.Padding.symmetric(vertical=4),
))
return ft.Container(
ft.Column([ft.Text("Properties", color=Palette.Overlay0, size=FontSize - 1), *Rows], spacing=2, tight=True),
bgcolor=Palette.Mantle,
+23 -2
View File
@@ -46,6 +46,23 @@ export function IsBoolean(Value) {
return /^(true|false)$/i.test(Value);
}
// What a value is, for its colour: quotes always make it text ("42" is text,
// 42 a number), like YAML. Same rules as the client's Properties.ValueKind.
export function ValueKind(Value) {
if (Value === "") return "Empty";
if (/^(["']).*\1$/.test(Value)) return "String";
if (IsBoolean(Value)) return "Boolean";
if (/^[+-]?\d+$/.test(Value)) return "Integer";
if (/^[+-]?(\d+\.\d*([eE][+-]?\d+)?|\.\d+([eE][+-]?\d+)?|\d+[eE][+-]?\d+)$/.test(Value)) return "Float";
if (/^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}(:\d{2})?)?$/.test(Value)) return "Date";
return "String";
}
// A value as shown: without the quotes that only said "this is text".
export function ShownValue(Value) {
return /^(["']).*\1$/.test(Value) ? Value.slice(1, -1) : Value;
}
// The note without its properties block (what Markdown renders).
export function WithoutProperties(Text) {
const Parsed = ParseProperties(Text);
@@ -99,9 +116,13 @@ export function PropertiesHtml(Parsed, RenderInline, Escape, CanEdit) {
const Disabled = CanEdit ? "" : " disabled";
Shown = `<input type="checkbox" class="PropertyCheckbox" data-line="${Line}"${Checked}${Disabled}>`;
} else if (List) {
Shown = List.map((Item) => `<span class="PropertyChip">${RenderInline(Item)}</span>`).join("");
Shown = List.map(
(Item) => `<span class="PropertyChip PropertyKind${ValueKind(Item)}">${RenderInline(ShownValue(Item))}</span>`,
).join("");
} else if (Value) {
Shown = `<span class="PropertyKind${ValueKind(Value)}">${RenderInline(ShownValue(Value))}</span>`;
} else {
Shown = Value ? RenderInline(Value) : `<span class="PropertyEmpty">Empty</span>`;
Shown = `<span class="PropertyEmpty">Empty</span>`;
}
return `<div class="PropertyRow"><span class="PropertyKey">${Escape(Key)}</span><span class="PropertyValue">${Shown}</span></div>`;
});
+23 -3
View File
@@ -527,7 +527,11 @@ img,
display: flex;
align-items: center;
gap: 12px;
padding: 2px 0;
padding: 4px 0;
}
.PropertyRow + .PropertyRow {
border-top: 1px solid var(--Border);
}
.PropertyKey {
@@ -536,7 +540,7 @@ img,
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--TextMuted);
color: var(--Text);
}
.PropertyValue {
@@ -552,7 +556,23 @@ img,
padding: 0 6px;
border-radius: 4px;
background: var(--SurfaceAlt);
color: var(--Purple);
}
/* Values by kind (Properties.js ValueKind): text, whole and decimal numbers, dates. */
.PropertyKindString {
color: var(--Green);
}
.PropertyKindInteger {
color: var(--Orange);
}
.PropertyKindFloat {
color: var(--Yellow);
}
.PropertyKindDate {
color: var(--Blue);
}
.PropertyEmpty {