mirror of
https://git.srhtnin.garden/sarah/SaraView.git
synced 2026-10-14 18:38:11 +02:00
Properties, and ticking checkboxes without editing (client)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
c8d29ad6f8
commit
ca37409e37
4 files changed
+245
-29
No files matched your search
+51
-19
@@ -7,7 +7,9 @@ A block is what Markdown itself separates: a heading line, a fenced code
|
||||
block, a horizontal rule, or a run of lines up to a blank line (a paragraph,
|
||||
list, table or quote). The text between blocks (blank lines) is kept
|
||||
byte for byte, so editing one block never reformats the rest of the file.
|
||||
A ```mermaid block shows as its diagram when MakeDiagram gives one (Diagrams.py)."""
|
||||
A ```mermaid block shows as its diagram when MakeDiagram gives one (Diagrams.py),
|
||||
and the properties block at the top as its panel (Properties.py). Checkboxes
|
||||
(tasks and true/false properties) tick without opening their block."""
|
||||
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
@@ -17,6 +19,7 @@ import flet as ft
|
||||
|
||||
import Core
|
||||
from Core import CodeStyleSheet, MarkdownCodeTheme, MarkdownSheet, Palette
|
||||
from Properties import ParseProperties, PropertiesView, ToggleLine, WithCheckboxes
|
||||
|
||||
FenceStart = re.compile(r"^ {0,3}(`{3,}|~{3,})")
|
||||
HeadingLine = re.compile(r"^ {0,3}(#{1,6})(\s|$)")
|
||||
@@ -28,7 +31,7 @@ IndentedLine = re.compile(r"^( {2,}|\t)\S")
|
||||
class Block:
|
||||
Start: int # offsets into the document; Text = Document[Start:End]
|
||||
End: int
|
||||
Kind: str # "Heading", "Code", "Rule" or "Text"
|
||||
Kind: str # "Properties", "Heading", "Code", "Rule" or "Text"
|
||||
StartLine: int
|
||||
Level: int = 0 # heading level
|
||||
|
||||
@@ -49,6 +52,11 @@ def SplitBlocks(Document: str) -> list[Block]:
|
||||
LastLine = -1 # last non-blank line of the open Text block
|
||||
Fence: Optional[tuple[str, int]] = None # (character, length) of an open code fence
|
||||
FenceFrom = 0
|
||||
Found = ParseProperties(Document)
|
||||
SkipUntil = -1
|
||||
if Found is not None:
|
||||
Blocks.append(Block(0, LineEnd(Found.EndLine), "Properties", 0))
|
||||
SkipUntil = Found.EndLine
|
||||
|
||||
def CloseCurrent() -> None:
|
||||
nonlocal Current
|
||||
@@ -57,6 +65,8 @@ def SplitBlocks(Document: str) -> list[Block]:
|
||||
Current = None
|
||||
|
||||
for Index, Line in enumerate(Lines):
|
||||
if Index <= SkipUntil:
|
||||
continue
|
||||
if Fence is not None:
|
||||
Match = FenceStart.match(Line)
|
||||
if Match and Match.group(1)[0] == Fence[0] and len(Match.group(1)) >= Fence[1] and not Line.strip()[len(Match.group(1)):].strip():
|
||||
@@ -116,7 +126,7 @@ class LiveMarkdown(ft.Column):
|
||||
self.BlockControls: list[ft.Control] = []
|
||||
# Rendered blocks by their text, reused when the layout is rebuilt so
|
||||
# only the edited part is rendered again.
|
||||
self.RenderCache: dict[str, list[ft.Control]] = {}
|
||||
self.RenderCache: dict[tuple[str, str], list[ft.Control]] = {}
|
||||
self.EditingIndex: Optional[int] = None # len(Blocks) is the new block at the end
|
||||
self.Field: Optional[ft.TextField] = None
|
||||
self._Layout(Update=False)
|
||||
@@ -219,24 +229,46 @@ class LiveMarkdown(ft.Column):
|
||||
|
||||
# --- Rendering -------------------------------------------------------
|
||||
|
||||
def _Render(self, Text: str) -> ft.Control:
|
||||
Diagram = self.MakeDiagram(Text, self.FontSize) if self.MakeDiagram else None
|
||||
Rendered = ft.Container(
|
||||
content=Diagram or ft.Markdown(
|
||||
value=Text,
|
||||
extension_set=ft.MarkdownExtensionSet.GITHUB_FLAVORED,
|
||||
md_style_sheet=MarkdownSheet(self.FontSize),
|
||||
code_theme=MarkdownCodeTheme(),
|
||||
code_style_sheet=CodeStyleSheet(),
|
||||
on_tap_link=self.OnTapLink,
|
||||
),
|
||||
padding=ft.Padding.symmetric(horizontal=8, vertical=6),
|
||||
border_radius=4,
|
||||
def _Markdown(self, Text: str) -> ft.Control:
|
||||
return ft.Markdown(
|
||||
value=Text,
|
||||
extension_set=ft.MarkdownExtensionSet.GITHUB_FLAVORED,
|
||||
md_style_sheet=MarkdownSheet(self.FontSize),
|
||||
code_theme=MarkdownCodeTheme(),
|
||||
code_style_sheet=CodeStyleSheet(),
|
||||
on_tap_link=self.OnTapLink,
|
||||
)
|
||||
|
||||
def _Render(self, Text: str, Kind: str) -> ft.Control:
|
||||
Rendered = ft.Container(padding=ft.Padding.symmetric(horizontal=8, vertical=6), border_radius=4)
|
||||
# Lines are counted from the block's start, found again when ticked:
|
||||
# a rendered block is reused wherever its text ends up (RenderCache).
|
||||
Toggle = lambda Line, C=Rendered: self._OnToggle(C, Line)
|
||||
Diagram = self.MakeDiagram(Text, self.FontSize) if self.MakeDiagram and Kind == "Code" else None
|
||||
if Diagram is not None:
|
||||
Rendered.content = Diagram
|
||||
elif Kind == "Properties" and (Found := ParseProperties(Text)) is not None:
|
||||
Rendered.content = PropertiesView(Found, self.FontSize, self._Markdown, Toggle)
|
||||
elif Kind == "Text":
|
||||
Parts = WithCheckboxes(Text, 0, self._Markdown, Toggle, self.FontSize)
|
||||
Rendered.content = Parts[0] if len(Parts) == 1 else ft.Column(Parts, spacing=0, tight=True)
|
||||
else:
|
||||
Rendered.content = self._Markdown(Text)
|
||||
Rendered.on_click = lambda e, C=Rendered: self._OnBlockClicked(C)
|
||||
Rendered.on_hover = lambda e, C=Rendered: self._OnHover(C, e)
|
||||
return Rendered
|
||||
|
||||
def _OnToggle(self, Control: ft.Control, Line: int) -> None:
|
||||
"""A checkbox in a rendered block: flip its line, leave the block shut."""
|
||||
if self.EditingIndex is not None:
|
||||
self.Commit()
|
||||
for Index, Controls in enumerate(self.BlockControls):
|
||||
if Controls is Control:
|
||||
self.Document = ToggleLine(self.Document, self.Blocks[Index].StartLine + Line)
|
||||
self.OnChange()
|
||||
self._Layout()
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def _OnHover(Control: ft.Container, Event) -> None:
|
||||
Control.bgcolor = Palette.Mantle if str(Event.data).lower() == "true" else None
|
||||
@@ -252,11 +284,11 @@ class LiveMarkdown(ft.Column):
|
||||
self.BlockControls.append(self.Field)
|
||||
continue
|
||||
Text = self.Document[Item.Start:Item.End]
|
||||
Reused = Cache.get(Text)
|
||||
Control = Reused.pop() if Reused else self._Render(Text)
|
||||
Reused = Cache.get((Item.Kind, Text))
|
||||
Control = Reused.pop() if Reused else self._Render(Text, Item.Kind)
|
||||
Control.bgcolor = None
|
||||
self.BlockControls.append(Control)
|
||||
self.RenderCache.setdefault(Text, []).append(Control)
|
||||
self.RenderCache.setdefault((Item.Kind, Text), []).append(Control)
|
||||
Controls = list(self.BlockControls)
|
||||
if self.EditingIndex == len(self.Blocks):
|
||||
Controls.append(self.Field)
|
||||
|
||||
Reference in new issue
Block a user