Telegram-iOS/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift
isaac 81e3be1923 InstantPage list checkboxes (task-list style): parse, serialize, render
Add a first-class `checked: Bool?` to InstantPageListItem (orthogonal to the
ordered-list `num`), replacing the prior sentinel-string-in-num prototype.

- Data model: enum third associated value on .text/.blocks; Postbox + a new
  FlatBuffers `checkState` tri-state field (0=nil,1=unchecked,2=checked,
  backward-compatible); Equatable.
- Transmission: read/write the native API `checkbox`=flags.0 / `checked`=flags.1
  bits on all four PageListItem / PageListOrderedItem constructors, so checkbox
  state survives the server round-trip for sender and recipients.
- Markdown: forward parser routes detected `[ ]`/`[x]` into `checked` (keeping
  the real number for ordered items); reverse converter emits `- [ ]`/`- [x]`
  so editing a rich message round-trips.
- Display: V1 + V2 layout detect via `item.checked`; the V2 renderer draws real
  CheckNode artwork (was a placeholder); preview text shows a checkbox glyph
  instead of leaking the old sentinel.

Spec: docs/superpowers/specs/2026-05-27-instantpage-list-checkbox-design.md
Plan: docs/superpowers/plans/2026-05-27-instantpage-list-checkbox.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 16:44:47 +02:00

79 lines
4.1 KiB
Swift

import Foundation
import SwiftSignalKit
import TelegramCore
import AccountContext
import InstantPageUI
import InstantPageCache
import UrlHandling
import TelegramUIPreferences
func faqSearchableItems(context: AccountContext, resolvedUrl: Signal<ResolvedUrl?, NoError>, suggestAccountDeletion: Bool) -> Signal<[SettingsSearchableItem], NoError> {
let strings = context.sharedContext.currentPresentationData.with { $0 }.strings
return resolvedUrl
|> map { resolvedUrl -> [SettingsSearchableItem] in
var results: [SettingsSearchableItem] = []
var nextIndex: Int32 = 2
if let resolvedUrl = resolvedUrl, case let .instantView(webPage, _) = resolvedUrl {
if case let .Loaded(content) = webPage.content, let instantPage = content.instantPage?._parse() {
var processingQuestions = false
var currentSection: String?
outer: for block in instantPage.blocks {
if !processingQuestions {
switch block {
case .blockQuote:
if results.isEmpty {
processingQuestions = true
}
default:
break
}
} else {
switch block {
case let .paragraph(text):
if case .bold = text {
currentSection = text.plainText
} else if case .concat = text {
processingQuestions = false
}
case let .list(items, false):
if let currentSection = currentSection {
for item in items {
if case let .text(itemText, _, _) = item, case let .url(text, url, _) = itemText {
let (_, anchor) = extractAnchor(string: url)
guard let anchor else {
continue
}
var index = nextIndex
if suggestAccountDeletion && anchor.contains("delete-my-account") {
index = 1
} else {
nextIndex += 1
}
let item = SettingsSearchableItem(
id: "faq/\(anchor)",
title: text.plainText,
alternate: [],
icon: .faq,
breadcrumbs: [strings.SettingsSearch_FAQ, currentSection],
present: { context, _, present in
let controller = context.sharedContext.makeInstantPageController(context: context, webPage: webPage, anchor: anchor, sourceLocation: InstantPageSourceLocation(userLocation: .other, peerType: .channel))
present(.push, controller)
})
if index == 1 {
results.insert(item, at: 0)
} else {
results.append(item)
}
}
}
}
default:
break
}
}
}
}
}
return results
}
}