Client: links and images open, line breaks like the web, directory notes setting

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
sarahandClaude Opus 5.5 committed 2026-10-13 18:22:51 +02:00
1 parent 55ee8e2f23
commit 0513d94c6a
8 files changed
+134 -23

No files matched your search

+42 -7
View File
@@ -18,17 +18,18 @@ EditorView/ImageView idea, with SaraView's View/Edit/Save toolbar).
- SaraTag tags (from the server's Books) show as chips under the toolbar."""
import re
from urllib.parse import unquote
from typing import Callable, Optional
import flet as ft
from App import App
from Core import ChipButton, CodeStyleSheet, HumanSize, MarkdownCodeTheme, MarkdownSheet, Palette
from Core import ChipButton, CodeStyleSheet, HardLineBreaks, HumanSize, MarkdownCodeTheme, MarkdownSheet, Palette
from Diagrams import MaybeDiagram, WithDiagrams
from Properties import ParseProperties, PropertiesView, ToggleLine, WithCheckboxes
from Variables import FindSpans, Substitute
from LiveMarkdown import LiveMarkdown
from Sources import Entry, SourceError, TextFileTypes
from Sources import Entry, NormalizeRelativePath, ParentPath, SourceError, TextFileTypes
LanguageByExtension = {
".json": "json", ".toml": "toml", ".yaml": "yaml", ".yml": "yaml", ".cfg": "ini", ".ini": "ini",
@@ -58,6 +59,7 @@ class FileView(ft.Container):
OnOpenExternally: Callable[[], None],
OnDownload: Optional[Callable[[], None]],
OnFocusChange: Callable[[bool], None],
OnOpenLink: Optional[Callable[[str, str], None]] = None,
):
self.TheApp = TheApp
self.Root = Root
@@ -66,6 +68,8 @@ class FileView(ft.Container):
self.FileType = Item.FileType or "unknown"
self.OnClose = OnClose
self.OnFocusChange = OnFocusChange
self.OnOpenLink = OnOpenLink # (Root, RelativePath): a link to another note, or an image, clicked
self.Pictures: dict[str, Optional[bytes]] = {} # images by path, read once
self.IsText = self.FileType in TextFileTypes
self.IsLive = (self.FileType == "markdown" and TheApp.Config.MarkdownEditing == "Live"
and TheApp.Source.CanWrite)
@@ -130,7 +134,8 @@ class FileView(ft.Container):
self.Live = LiveMarkdown(self.Content, self.TheApp.Config.FontSize, self._OnTextChanged,
self.OnFocusChange, self._OnTapLink,
MakeDiagram=lambda Text, Size: MaybeDiagram(self.TheApp, Text, Size),
GetSpans=lambda Document: FindSpans(self.TheApp, self.Root, self.RelativePath, Document))
GetSpans=lambda Document: FindSpans(self.TheApp, self.Root, self.RelativePath, Document),
MakeImage=self._Image)
return self.Live
return self._RenderedView()
if self.FileType == "image":
@@ -152,7 +157,7 @@ class FileView(ft.Container):
def MakeMarkdown(Part: str) -> ft.Control:
return ft.Markdown(
value=Part,
value=HardLineBreaks(Part),
selectable=True,
extension_set=ft.MarkdownExtensionSet.GITHUB_FLAVORED,
md_style_sheet=MarkdownSheet(FontSize),
@@ -164,7 +169,7 @@ class FileView(ft.Container):
def MakeWithCheckboxes(Part: str, Start: int) -> ft.Control:
FirstLine = Text[:BodyStart + Start].count("\n")
Shown, LineMap = Substitute(Part, BodyStart + Start, Spans) # variables as their results
Parts = WithCheckboxes(Shown, FirstLine, MakeMarkdown, Toggle, FontSize, LineMap)
Parts = WithCheckboxes(Shown, FirstLine, MakeMarkdown, Toggle, FontSize, LineMap, self._Image)
return Parts[0] if len(Parts) == 1 else ft.Column(Parts, spacing=0, tight=True)
Views = WithDiagrams(self.TheApp, Text[BodyStart:], FontSize, MakeWithCheckboxes)
@@ -220,9 +225,39 @@ class FileView(ft.Container):
return ft.Row(Chips, wrap=True, spacing=4, run_spacing=4)
def _OnTapLink(self, e) -> None:
Target = str(e.data or "")
if Target.startswith(("http://", "https://", "mailto:")):
self.OpenTarget(str(e.data or ""))
def OpenTarget(self, Target: str) -> None:
"""A clicked link or image: web addresses in the browser, a note (or
directory, or image) relative to this one in SaraView."""
if not Target or Target.startswith("#"):
return
if re.match(r"^[a-z][a-z0-9+.-]*:", Target, re.IGNORECASE):
self.TheApp.Page.run_task(ft.UrlLauncher().launch_url, Target)
elif self.OnOpenLink is not None:
Path = NormalizeRelativePath(ParentPath(self.RelativePath), unquote(Target.split("#", 1)[0]))
self.OnOpenLink(self.Root, Path)
def _Image(self, Src: str, Alt: str) -> Optional[ft.Control]:
"""A line that's only an image (Properties.WithCheckboxes): the image,
opened when clicked. None if it can't be read: then it shows as written."""
if re.match(r"^https?://", Src):
Picture: ft.Image = ft.Image(src=Src, fit=ft.BoxFit.CONTAIN, tooltip=Alt or None)
else:
Path = NormalizeRelativePath(ParentPath(self.RelativePath), unquote(Src))
if Path not in self.Pictures:
try:
self.Pictures[Path] = self.TheApp.Source.ReadBytes(self.Root, Path)
except SourceError:
self.Pictures[Path] = None
if self.Pictures[Path] is None:
return None
Picture = ft.Image(src=self.Pictures[Path], fit=ft.BoxFit.CONTAIN, tooltip=Alt or None)
return ft.Container(
ft.GestureDetector(content=Picture, on_tap=lambda e: self.OpenTarget(Src), mouse_cursor=ft.MouseCursor.CLICK),
alignment=ft.Alignment.CENTER_LEFT,
padding=ft.Padding.symmetric(vertical=4),
)
def _ToggleAndSave(self, Line: int) -> None:
"""A checkbox ticked in View: flip its line and save, like the web view."""