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)
|
.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
|
@ViewBuilder
|
||||||
private func lineView(_ raw: String) -> some View {
|
private func lineView(_ raw: String) -> some View {
|
||||||
let trimmed = raw.trimmingCharacters(in: .whitespaces)
|
switch Self.classify(raw) {
|
||||||
if trimmed.isEmpty {
|
case .blank:
|
||||||
Color.clear.frame(height: 3)
|
Color.clear.frame(height: 3)
|
||||||
} else if trimmed.hasPrefix("### ") {
|
case .heading(let content, let scale, let weight):
|
||||||
// Headings scale relative to the base prose size so the hierarchy holds
|
inline(content).font(.system(size: bodySize * scale, weight: weight))
|
||||||
// at any base and never collapses to the body size.
|
case .bullet(let content):
|
||||||
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) {
|
|
||||||
HStack(alignment: .firstTextBaseline, spacing: 6) {
|
HStack(alignment: .firstTextBaseline, spacing: 6) {
|
||||||
Text("•").foregroundStyle(.secondary)
|
Text("•").foregroundStyle(.secondary)
|
||||||
inline(bullet)
|
inline(content)
|
||||||
}
|
}
|
||||||
} else {
|
case .plain(let content):
|
||||||
inline(raw)
|
inline(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user