oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1import Foundation
2
3/// The optimistic string mirrors the engine's block splice, while the next
4/// acknowledged projection supplies fonts, list markers and image attachments.
5@MainActor enum DocumentClipboardInsertion {
6 static func text(blocks: [JSONValue], in content: NSAttributedString, range: NSRange) -> String? {
7 guard !blocks.isEmpty, range.location >= 0, range.length >= 0,
8 range.location <= content.length, range.length <= content.length - range.location else { return nil }
9 var budget = 100_000
10 guard let text = flatten(.object(["type": .string("fragment"), "children": .array(blocks)]), depth: 0, budget: &budget) else { return nil }
11 let firstIsText = ["paragraph", "heading", "code_block"].contains(type(blocks[0]))
12 let lastHasText = containsTextBlock(blocks[blocks.count - 1])
13 let raw = content.string as NSString
14 let paragraph = raw.paragraphRange(for: NSRange(location: min(range.location, max(0, raw.length - 1)), length: 0))
15 let prefixLength = range.location - paragraph.location
16 let prefix = !firstIsText && prefixLength > 0 ? "\n" : ""
17 return prefix + (lastHasText && text.hasSuffix("\n") ? String(text.dropLast()) : text)
18 }
19 static func attributed(blocks: [JSONValue], in content: NSAttributedString, range: NSRange) -> NSAttributedString? {
20 guard let expected = text(blocks: blocks, in: content, range: range) else { return nil }
21 var counter = 0
22 func identified(_ value: JSONValue) -> JSONValue {
23 guard case .object(var node) = value else { return value }
24 counter += 1; node["id"] = .string("clipboard-\(counter)")
25 if case .array(let children)? = node["children"] { node["children"] = .array(children.map(identified)) }
26 return .object(node)
27 }
28 let value: [String: JSONValue] = ["children": .array(blocks.map(identified)), "canUndo": .bool(false), "canRedo": .bool(false)]
29 guard let data = try? JSONEncoder().encode(value), let projection = try? JSONDecoder().decode(CRDTProjection.self, from: data) else { return nil }
30 let rendered = NSMutableAttributedString(attributedString: NativeTextProjection.render(projection))
31 if containsTextBlock(blocks[blocks.count - 1]), rendered.string.hasSuffix("\n") { rendered.deleteCharacters(in: NSRange(location: rendered.length - 1, length: 1)) }
32 if expected.hasPrefix("\n"), !rendered.string.hasPrefix("\n"), expected == "\n" + rendered.string { rendered.insert(NSAttributedString(string: "\n"), at: 0) }
33 guard rendered.string == expected else { return nil }
34 return rendered
35 }
36 private static func type(_ value: JSONValue) -> String {
37 guard case .object(let node) = value, case .string(let type)? = node["type"] else { return "" }; return type
38 }
39 private static func containsTextBlock(_ value: JSONValue) -> Bool {
40 guard case .object(let node) = value else { return false }
41 if ["paragraph", "heading", "code_block"].contains(type(value)) { return true }
42 guard case .array(let children)? = node["children"], let last = children.last else { return false }
43 return containsTextBlock(last)
44 }
45 private static func flatten(_ value: JSONValue, depth: Int, budget: inout Int) -> String? {
46 guard depth <= 64, budget > 0, case .object(let node) = value else { return nil }
47 budget -= 1
48 let kind = type(value)
49 if kind == "text" {
50 guard case .array(let runs)? = node["runs"] else { return nil }
51 var text = ""
52 for run in runs {
53 guard case .object(let run) = run, case .string(let insert)? = run["insert"] else { return nil }
54 text += insert
55 }
56 return text
57 }
58 if kind == "hard_break" { return "\u{2028}" }
59 if kind == "image" { return "\u{fffc}" }
60 if kind == "horizontal_rule" { return "\u{fffc}\n" }
61 guard ["fragment", "paragraph", "heading", "code_block", "blockquote", "bullet_list", "ordered_list", "list_item"].contains(kind) else { return nil }
62 var result = ""
63 if case .array(let children)? = node["children"] {
64 for child in children { guard let text = flatten(child, depth: depth + 1, budget: &budget) else { return nil }; result += text }
65 }
66 if ["paragraph", "heading", "code_block"].contains(kind) { result += "\n" }
67 return result
68 }
69}
70