Session View Animation
Nucleic-Session: A8FFA1A6-443F-444A-A735-C53958D7E570 Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
@@ -167,26 +167,54 @@ struct MarkdownText: View {
|
||||
.lineSpacing(4)
|
||||
}
|
||||
|
||||
/// One prose line's classification: the exact string that gets inline-parsed — the cache key
|
||||
/// `prewarm` must match — paired with how it's styled. Single source of truth for both the
|
||||
/// live `lineView` and the off-main `prewarm`, so a warmed key can never drift from the key
|
||||
/// the layout later looks up (any drift and the warm entry would silently miss).
|
||||
private enum LineStyle {
|
||||
case blank
|
||||
case heading(content: String, scale: CGFloat, weight: Font.Weight)
|
||||
case bullet(content: String)
|
||||
case plain(content: String)
|
||||
|
||||
/// The string handed to `inline(_:)`, or nil for a line that renders without a parse.
|
||||
var inlineContent: String? {
|
||||
switch self {
|
||||
case .blank: return nil
|
||||
case .heading(let c, _, _), .bullet(let c), .plain(let c): return c
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Classify one raw prose line. Headings are checked before bullets (a heading marker wins),
|
||||
/// and the plain case keeps the *raw* line (not the trimmed one) exactly as the old cascade
|
||||
/// did — the inline parser preserves leading whitespace under `.inlineOnlyPreservingWhitespace`.
|
||||
private static func classify(_ raw: String) -> LineStyle {
|
||||
let trimmed = raw.trimmingCharacters(in: .whitespaces)
|
||||
if trimmed.isEmpty { return .blank }
|
||||
// Headings scale relative to the base prose size so the hierarchy holds at any base and
|
||||
// never collapses to the body size.
|
||||
if trimmed.hasPrefix("### ") { return .heading(content: String(trimmed.dropFirst(4)), scale: 1.13, weight: .semibold) }
|
||||
if trimmed.hasPrefix("## ") { return .heading(content: String(trimmed.dropFirst(3)), scale: 1.28, weight: .bold) }
|
||||
if trimmed.hasPrefix("# ") { return .heading(content: String(trimmed.dropFirst(2)), scale: 1.5, weight: .bold) }
|
||||
if let bullet = bulletContent(trimmed) { return .bullet(content: bullet) }
|
||||
return .plain(content: raw)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func lineView(_ raw: String) -> some View {
|
||||
let trimmed = raw.trimmingCharacters(in: .whitespaces)
|
||||
if trimmed.isEmpty {
|
||||
switch Self.classify(raw) {
|
||||
case .blank:
|
||||
Color.clear.frame(height: 3)
|
||||
} else if trimmed.hasPrefix("### ") {
|
||||
// Headings scale relative to the base prose size so the hierarchy holds
|
||||
// at any base and never collapses to the body size.
|
||||
inline(String(trimmed.dropFirst(4))).font(.system(size: bodySize * 1.13, weight: .semibold))
|
||||
} else if trimmed.hasPrefix("## ") {
|
||||
inline(String(trimmed.dropFirst(3))).font(.system(size: bodySize * 1.28, weight: .bold))
|
||||
} else if trimmed.hasPrefix("# ") {
|
||||
inline(String(trimmed.dropFirst(2))).font(.system(size: bodySize * 1.5, weight: .bold))
|
||||
} else if let bullet = Self.bulletContent(trimmed) {
|
||||
case .heading(let content, let scale, let weight):
|
||||
inline(content).font(.system(size: bodySize * scale, weight: weight))
|
||||
case .bullet(let content):
|
||||
HStack(alignment: .firstTextBaseline, spacing: 6) {
|
||||
Text("•").foregroundStyle(.secondary)
|
||||
inline(bullet)
|
||||
inline(content)
|
||||
}
|
||||
} else {
|
||||
inline(raw)
|
||||
case .plain(let content):
|
||||
inline(content)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user