Add live Markdown editing, colours and files in the tree

This commit is contained in:
sarah committed 2026-10-09 09:03:23 +02:00
1 parent 0eecfba224
commit f851ecd820
15 files changed
+1025 -58

No files matched your search

+58 -6
View File
@@ -4,6 +4,9 @@ EditorView/ImageView idea, with SaraView's View/Edit/Save toolbar).
- Markdown is rendered in View; the other text types are shown as
highlighted code. Edit is a plain monospace field with an explicit Save
(Ctrl+S). On a server, saving records line blame for you, like the web view.
- With Markdown editing set to Live (Settings > General), Markdown opens in
LiveMarkdown instead: rendered, and click a block to edit it in place.
Edit/View becomes Source/Live there.
- Images are shown fitted to the window.
- Video and other files get their details and Open Externally (a server
file is downloaded to ~/.cache/SaraView first). Flet has no built-in
@@ -16,7 +19,8 @@ from typing import Callable, Optional
import flet as ft
from App import App
from Core import ChipButton, CodeStyleSheet, HumanSize, Palette
from Core import ChipButton, CodeStyleSheet, HumanSize, MarkdownCodeTheme, MarkdownSheet, Palette
from LiveMarkdown import LiveMarkdown
from Sources import Entry, SourceError, TextFileTypes
LanguageByExtension = {
@@ -56,6 +60,9 @@ class FileView(ft.Container):
self.OnClose = OnClose
self.OnFocusChange = OnFocusChange
self.IsText = self.FileType in TextFileTypes
self.IsLive = (self.FileType == "markdown" and TheApp.Config.MarkdownEditing == "Live"
and TheApp.Source.CanWrite)
self.Live: Optional[LiveMarkdown] = None
self.IsEditing = False
self.IsDirty = False
self.Content = ""
@@ -63,6 +70,9 @@ class FileView(ft.Container):
self.StatusText = ft.Text("", color=Palette.Overlay0, size=12)
self.ToggleButton = ChipButton("Edit", self._ToggleEdit, Icon=ft.Icons.EDIT_OUTLINED)
if self.IsLive:
self.ToggleButton.content = "Source"
self.ToggleButton.icon = ft.Icons.CODE
self.SaveButton = ChipButton("Save", lambda e: self.Save(), Icon=ft.Icons.SAVE_OUTLINED, Disabled=True)
CanWrite = TheApp.Source.CanWrite
if not CanWrite:
@@ -109,6 +119,10 @@ class FileView(ft.Container):
if self.IsText:
self.Content = Source.ReadText(self.Root, self.RelativePath)
self.TextField.value = self.Content
if self.IsLive:
self.Live = LiveMarkdown(self.Content, self.TheApp.Config.FontSize, self._OnTextChanged,
self.OnFocusChange, self._OnTapLink)
return self.Live
return self._RenderedView()
if self.FileType == "image":
return ft.Image(src=Source.ReadBytes(self.Root, self.RelativePath), fit=ft.BoxFit.CONTAIN, expand=True)
@@ -124,7 +138,8 @@ class FileView(ft.Container):
value=Text,
selectable=True,
extension_set=ft.MarkdownExtensionSet.GITHUB_FLAVORED,
code_theme=ft.MarkdownCodeTheme.ATOM_ONE_DARK,
md_style_sheet=MarkdownSheet(self.TheApp.Config.FontSize),
code_theme=MarkdownCodeTheme(),
code_style_sheet=CodeStyleSheet(),
on_tap_link=self._OnTapLink,
)
@@ -134,7 +149,8 @@ class FileView(ft.Container):
value=FencedBlock(Text, LanguageByExtension.get(Extension, "")),
selectable=True,
extension_set=ft.MarkdownExtensionSet.GITHUB_FLAVORED,
code_theme=ft.MarkdownCodeTheme.ATOM_ONE_DARK,
md_style_sheet=MarkdownSheet(self.TheApp.Config.FontSize),
code_theme=MarkdownCodeTheme(),
code_style_sheet=CodeStyleSheet(),
)
else:
@@ -186,7 +202,7 @@ class FileView(ft.Container):
def CanSave(self) -> bool:
return self.IsText and self.IsDirty
def _OnTextChanged(self, e) -> None:
def _OnTextChanged(self, e=None) -> None:
if not self.IsDirty:
self.IsDirty = True
self.SaveButton.disabled = False
@@ -194,7 +210,17 @@ class FileView(ft.Container):
self.SaveButton.update()
self.StatusText.update()
@property
def Text(self) -> str:
"""What's being edited, in whichever view has it."""
if self.Live is not None and not self.IsEditing:
return self.Live.Text
return self.TextField.value or ""
def _ToggleEdit(self, e=None) -> None:
if self.Live is not None:
self._ToggleSource()
return
self.IsEditing = not self.IsEditing
if self.IsEditing:
self.Body.content = self.TextField
@@ -210,6 +236,32 @@ class FileView(ft.Container):
self.Body.update()
self.ToggleButton.update()
def _ToggleSource(self) -> None:
"""Live mode's toggle: the whole file as plain text, and back."""
self.IsEditing = not self.IsEditing
if self.IsEditing:
self.TextField.value = self.Live.Text
self.Live.EditingIndex = None # its open block's text is in the field now
self.Body.content = self.TextField
self.ToggleButton.content = "Live"
self.ToggleButton.icon = ft.Icons.VISIBILITY_OUTLINED
self.OnFocusChange(True)
else:
self.Live.SetText(self.TextField.value or "", Update=False) # not on the page yet
self.Body.content = self.Live
self.ToggleButton.content = "Source"
self.ToggleButton.icon = ft.Icons.CODE
self.OnFocusChange(False)
self.Body.update()
self.ToggleButton.update()
def HandleEscape(self) -> bool:
"""Esc first closes an open block in live mode; True if it did."""
if self.Live is not None and not self.IsEditing and self.Live.IsEditing:
self.Live.Commit()
return True
return False
def ToggleEdit(self) -> None:
if self.IsText and not self.ToggleButton.disabled:
self._ToggleEdit()
@@ -218,11 +270,11 @@ class FileView(ft.Container):
if not self.CanSave:
return
try:
self.TheApp.Source.WriteText(self.Root, self.RelativePath, self.TextField.value or "")
self.TheApp.Source.WriteText(self.Root, self.RelativePath, self.Text)
except SourceError as Error:
self.TheApp.HandleError(Error, f"Couldn't save {self.Item.Name}")
return
self.Content = self.TextField.value or ""
self.Content = self.Text
self.IsDirty = False
self.SaveButton.disabled = True
self.StatusText.value = "Saved."