/// The three views of a list, named as the contract's `filter` field names them. public enum Filter: String, Sendable, CaseIterable { case all, active, completed /// A missing or unknown value means `all`. init(field: String?) { self = field.flatMap(Filter.init(rawValue:)) ?? .all } var path: String { switch self { case .all: "/" case .active: "/active" case .completed: "/completed" } } func includes(_ todo: Todo) -> Bool { switch self { case .all: true case .active: !todo.completed case .completed: todo.completed } } } /// Server-rendered HTML. Everything a visitor typed goes through `escape`. /// /// The markup follows `shared/reference.html`. The controls marked /// `class="nojs"` are what a page without JavaScript uses; `app.css` lays the /// toggle buttons exactly over the checkboxes they stand in for, and hides /// every `nojs` control once the `js` class is on ``. enum Page { static let language = "Swift" static func document(todos: [Todo], filter: Filter, editing: Int?, version: String) -> String { var html = """ TodoMVC

todos

\(filterField(filter))
""" html += body(todos: todos, filter: filter, editing: editing) html += """
""" return html } /// `section.main` and `footer.footer`, or nothing when the list is empty. /// The page's JavaScript swaps this in after each action. static func body(todos: [Todo], filter: Filter, editing: Int?) -> String { guard !todos.isEmpty else { return "" } let left = todos.count { !$0.completed } let done = todos.count - left var html = """
\(filterField(filter))
\n" return html } static func item(_ todo: Todo, filter: Filter, editing: Bool) -> String { let id = todo.id let title = escape(todo.title) var classes: [String] = [] if todo.completed { classes.append("completed") } if editing { classes.append("editing") } let classAttr = classes.isEmpty ? "" : " class=\"\(classes.joined(separator: " "))\"" let editHref = "\(filter.path)?edit=\(id)" return """
✎
\(filterField(filter))
""" } static func filterField(_ filter: Filter) -> String { "" } /// Escapes text for use in HTML content and in double-quoted attributes. static func escape(_ text: String) -> String { var out = "" out.reserveCapacity(text.utf8.count) for c in text.unicodeScalars { switch c { case "&": out += "&" case "<": out += "<" case ">": out += ">" case "\"": out += """ case "'": out += "'" default: out.unicodeScalars.append(c) } } return out } }