import Foundation /// The optimistic string mirrors the engine's block splice, while the next /// acknowledged projection supplies fonts, list markers and image attachments. @MainActor enum DocumentClipboardInsertion { static func text(blocks: [JSONValue], in content: NSAttributedString, range: NSRange) -> String? { guard !blocks.isEmpty, range.location >= 0, range.length >= 0, range.location <= content.length, range.length <= content.length - range.location else { return nil } var budget = 100_000 guard let text = flatten(.object(["type": .string("fragment"), "children": .array(blocks)]), depth: 0, budget: &budget) else { return nil } let firstIsText = ["paragraph", "heading", "code_block"].contains(type(blocks[0])) let lastHasText = containsTextBlock(blocks[blocks.count - 1]) let raw = content.string as NSString let paragraph = raw.paragraphRange(for: NSRange(location: min(range.location, max(0, raw.length - 1)), length: 0)) let prefixLength = range.location - paragraph.location let prefix = !firstIsText && prefixLength > 0 ? "\n" : "" return prefix + (lastHasText && text.hasSuffix("\n") ? String(text.dropLast()) : text) } static func attributed(blocks: [JSONValue], in content: NSAttributedString, range: NSRange) -> NSAttributedString? { guard let expected = text(blocks: blocks, in: content, range: range) else { return nil } var counter = 0 func identified(_ value: JSONValue) -> JSONValue { guard case .object(var node) = value else { return value } counter += 1; node["id"] = .string("clipboard-\(counter)") if case .array(let children)? = node["children"] { node["children"] = .array(children.map(identified)) } return .object(node) } let value: [String: JSONValue] = ["children": .array(blocks.map(identified)), "canUndo": .bool(false), "canRedo": .bool(false)] guard let data = try? JSONEncoder().encode(value), let projection = try? JSONDecoder().decode(CRDTProjection.self, from: data) else { return nil } let rendered = NSMutableAttributedString(attributedString: NativeTextProjection.render(projection)) if containsTextBlock(blocks[blocks.count - 1]), rendered.string.hasSuffix("\n") { rendered.deleteCharacters(in: NSRange(location: rendered.length - 1, length: 1)) } if expected.hasPrefix("\n"), !rendered.string.hasPrefix("\n"), expected == "\n" + rendered.string { rendered.insert(NSAttributedString(string: "\n"), at: 0) } guard rendered.string == expected else { return nil } return rendered } private static func type(_ value: JSONValue) -> String { guard case .object(let node) = value, case .string(let type)? = node["type"] else { return "" }; return type } private static func containsTextBlock(_ value: JSONValue) -> Bool { guard case .object(let node) = value else { return false } if ["paragraph", "heading", "code_block"].contains(type(value)) { return true } guard case .array(let children)? = node["children"], let last = children.last else { return false } return containsTextBlock(last) } private static func flatten(_ value: JSONValue, depth: Int, budget: inout Int) -> String? { guard depth <= 64, budget > 0, case .object(let node) = value else { return nil } budget -= 1 let kind = type(value) if kind == "text" { guard case .array(let runs)? = node["runs"] else { return nil } var text = "" for run in runs { guard case .object(let run) = run, case .string(let insert)? = run["insert"] else { return nil } text += insert } return text } if kind == "hard_break" { return "\u{2028}" } if kind == "image" { return "\u{fffc}" } if kind == "horizontal_rule" { return "\u{fffc}\n" } guard ["fragment", "paragraph", "heading", "code_block", "blockquote", "bullet_list", "ordered_list", "list_item"].contains(kind) else { return nil } var result = "" if case .array(let children)? = node["children"] { for child in children { guard let text = flatten(child, depth: depth + 1, budget: &budget) else { return nil }; result += text } } if ["paragraph", "heading", "code_block"].contains(kind) { result += "\n" } return result } }