@openeditor/react-native-prose-editor 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +160 -0
- package/README.md +180 -0
- package/android/build.gradle +69 -0
- package/android/consumer-rules.pro +18 -0
- package/android/src/androidTest/AndroidManifest.xml +8 -0
- package/android/src/androidTest/java/com/apollohg/editor/MeasureHeightInstrumentedTest.kt +53 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceCollaborationInitialSyncTest.kt +241 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceImeRegressionTest.kt +338 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceOutsideTapTest.kt +789 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeDevicePerformanceTest.kt +350 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeEditorOutsideTapActivity.kt +5 -0
- package/android/src/main/assets/editor-icons/MaterialIcons.json +2236 -0
- package/android/src/main/assets/editor-icons/MaterialIcons.ttf +0 -0
- package/android/src/main/java/com/apollohg/editor/CaretGeometry.kt +50 -0
- package/android/src/main/java/com/apollohg/editor/EditorAddons.kt +135 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +4527 -0
- package/android/src/main/java/com/apollohg/editor/EditorHeightBehavior.kt +14 -0
- package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +999 -0
- package/android/src/main/java/com/apollohg/editor/EditorTheme.kt +474 -0
- package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +199 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +3137 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +561 -0
- package/android/src/main/java/com/apollohg/editor/NativeProseViewerExpoView.kt +315 -0
- package/android/src/main/java/com/apollohg/editor/NativeToolbar.kt +1413 -0
- package/android/src/main/java/com/apollohg/editor/PositionBridge.kt +133 -0
- package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +315 -0
- package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +2172 -0
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +399 -0
- package/android/src/sharedTest/java/com/apollohg/editor/NativePerformanceSupport.kt +482 -0
- package/android/src/test/java/com/apollohg/editor/CaretGeometryTest.kt +137 -0
- package/android/src/test/java/com/apollohg/editor/EditorEditTextHardwareKeyTest.kt +321 -0
- package/android/src/test/java/com/apollohg/editor/EditorInputConnectionTest.kt +4202 -0
- package/android/src/test/java/com/apollohg/editor/NativeEditorExpoViewTest.kt +2581 -0
- package/android/src/test/java/com/apollohg/editor/NativeEditorModuleTest.kt +27 -0
- package/android/src/test/java/com/apollohg/editor/NativePerformanceTest.kt +197 -0
- package/android/src/test/java/com/apollohg/editor/NativeProseViewerExpoViewTest.kt +84 -0
- package/android/src/test/java/com/apollohg/editor/NativeToolbarTest.kt +544 -0
- package/android/src/test/java/com/apollohg/editor/PositionBridgeTest.kt +461 -0
- package/android/src/test/java/com/apollohg/editor/RenderBridgeTest.kt +2054 -0
- package/android/src/test/java/com/apollohg/editor/RichTextEditorViewTest.kt +1367 -0
- package/app.plugin.js +62 -0
- package/dist/EditorTheme.d.ts +113 -0
- package/dist/EditorTheme.js +29 -0
- package/dist/EditorToolbar.d.ts +191 -0
- package/dist/EditorToolbar.js +1095 -0
- package/dist/NativeEditorBridge.d.ts +325 -0
- package/dist/NativeEditorBridge.js +933 -0
- package/dist/NativeProseViewer.d.ts +53 -0
- package/dist/NativeProseViewer.js +480 -0
- package/dist/NativeRichTextEditor.d.ts +183 -0
- package/dist/NativeRichTextEditor.js +2574 -0
- package/dist/YjsCollaboration.d.ts +90 -0
- package/dist/YjsCollaboration.js +743 -0
- package/dist/addons.d.ts +100 -0
- package/dist/addons.js +82 -0
- package/dist/heightCache.d.ts +6 -0
- package/dist/heightCache.js +45 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +30 -0
- package/dist/schemas.d.ts +37 -0
- package/dist/schemas.js +263 -0
- package/dist/useNativeEditor.d.ts +42 -0
- package/dist/useNativeEditor.js +124 -0
- package/expo-module.config.json +9 -0
- package/ios/EditorAddons.swift +327 -0
- package/ios/EditorCore.xcframework/Info.plist +44 -0
- package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
- package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
- package/ios/EditorLayoutManager.swift +774 -0
- package/ios/EditorTheme.swift +515 -0
- package/ios/Generated_editor_core.swift +1513 -0
- package/ios/NativeEditorExpoView.swift +3593 -0
- package/ios/NativeEditorModule.swift +624 -0
- package/ios/NativeProseViewerExpoView.swift +276 -0
- package/ios/PositionBridge.swift +558 -0
- package/ios/ReactNativeProseEditor.podspec +49 -0
- package/ios/RenderBridge.swift +1708 -0
- package/ios/RichTextEditorView.swift +6150 -0
- package/ios/Tests/NativePerformanceTests.swift +687 -0
- package/ios/Tests/PositionBridgeTests.swift +706 -0
- package/ios/Tests/RenderBridgeTests.swift +2236 -0
- package/ios/Tests/RichTextEditorViewTests.swift +6548 -0
- package/ios/editor_coreFFI/editor_coreFFI.h +1289 -0
- package/ios/editor_coreFFI/module.modulemap +7 -0
- package/ios/editor_coreFFI.h +904 -0
- package/ios/editor_coreFFI.modulemap +7 -0
- package/package.json +74 -0
- package/rust/android/arm64-v8a/libeditor_core.so +0 -0
- package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
- package/rust/android/x86_64/libeditor_core.so +0 -0
- package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +2563 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
struct EditorTextStyle {
|
|
4
|
+
var fontFamily: String?
|
|
5
|
+
var fontSize: CGFloat?
|
|
6
|
+
var fontWeight: String?
|
|
7
|
+
var fontStyle: String?
|
|
8
|
+
var color: UIColor?
|
|
9
|
+
var lineHeight: CGFloat?
|
|
10
|
+
var spacingAfter: CGFloat?
|
|
11
|
+
|
|
12
|
+
init(
|
|
13
|
+
fontFamily: String? = nil,
|
|
14
|
+
fontSize: CGFloat? = nil,
|
|
15
|
+
fontWeight: String? = nil,
|
|
16
|
+
fontStyle: String? = nil,
|
|
17
|
+
color: UIColor? = nil,
|
|
18
|
+
lineHeight: CGFloat? = nil,
|
|
19
|
+
spacingAfter: CGFloat? = nil
|
|
20
|
+
) {
|
|
21
|
+
self.fontFamily = fontFamily
|
|
22
|
+
self.fontSize = fontSize
|
|
23
|
+
self.fontWeight = fontWeight
|
|
24
|
+
self.fontStyle = fontStyle
|
|
25
|
+
self.color = color
|
|
26
|
+
self.lineHeight = lineHeight
|
|
27
|
+
self.spacingAfter = spacingAfter
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
init(dictionary: [String: Any]) {
|
|
31
|
+
fontFamily = dictionary["fontFamily"] as? String
|
|
32
|
+
fontSize = EditorTheme.cgFloat(dictionary["fontSize"])
|
|
33
|
+
fontWeight = dictionary["fontWeight"] as? String
|
|
34
|
+
fontStyle = dictionary["fontStyle"] as? String
|
|
35
|
+
color = EditorTheme.color(from: dictionary["color"])
|
|
36
|
+
lineHeight = EditorTheme.cgFloat(dictionary["lineHeight"])
|
|
37
|
+
spacingAfter = EditorTheme.cgFloat(dictionary["spacingAfter"])
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func merged(with override: EditorTextStyle?) -> EditorTextStyle {
|
|
41
|
+
guard let override else { return self }
|
|
42
|
+
return EditorTextStyle(
|
|
43
|
+
fontFamily: override.fontFamily ?? fontFamily,
|
|
44
|
+
fontSize: override.fontSize ?? fontSize,
|
|
45
|
+
fontWeight: override.fontWeight ?? fontWeight,
|
|
46
|
+
fontStyle: override.fontStyle ?? fontStyle,
|
|
47
|
+
color: override.color ?? color,
|
|
48
|
+
lineHeight: override.lineHeight ?? lineHeight,
|
|
49
|
+
spacingAfter: override.spacingAfter ?? spacingAfter
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func resolvedFont(fallback: UIFont) -> UIFont {
|
|
54
|
+
let size = fontSize ?? fallback.pointSize
|
|
55
|
+
var font = fallback.withSize(size)
|
|
56
|
+
|
|
57
|
+
if let fontFamily,
|
|
58
|
+
let familyFont = UIFont(name: fontFamily, size: size) {
|
|
59
|
+
font = familyFont
|
|
60
|
+
} else if let fontWeight {
|
|
61
|
+
font = UIFont.systemFont(ofSize: size, weight: EditorTheme.fontWeight(from: fontWeight))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
var traits = font.fontDescriptor.symbolicTraits
|
|
65
|
+
if EditorTheme.shouldApplyBoldTrait(fontWeight) {
|
|
66
|
+
traits.insert(.traitBold)
|
|
67
|
+
}
|
|
68
|
+
if fontStyle == "italic" {
|
|
69
|
+
traits.insert(.traitItalic)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if traits != font.fontDescriptor.symbolicTraits,
|
|
73
|
+
let descriptor = font.fontDescriptor.withSymbolicTraits(traits) {
|
|
74
|
+
font = UIFont(descriptor: descriptor, size: size)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return font
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
struct EditorListTheme {
|
|
82
|
+
var indent: CGFloat?
|
|
83
|
+
var baseIndentMultiplier: CGFloat?
|
|
84
|
+
var itemSpacing: CGFloat?
|
|
85
|
+
var markerColor: UIColor?
|
|
86
|
+
var markerScale: CGFloat?
|
|
87
|
+
|
|
88
|
+
init(dictionary: [String: Any]) {
|
|
89
|
+
indent = EditorTheme.cgFloat(dictionary["indent"])
|
|
90
|
+
baseIndentMultiplier = EditorTheme.cgFloat(dictionary["baseIndentMultiplier"])
|
|
91
|
+
itemSpacing = EditorTheme.cgFloat(dictionary["itemSpacing"])
|
|
92
|
+
markerColor = EditorTheme.color(from: dictionary["markerColor"])
|
|
93
|
+
markerScale = EditorTheme.cgFloat(dictionary["markerScale"])
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
struct EditorHorizontalRuleTheme {
|
|
98
|
+
var color: UIColor?
|
|
99
|
+
var thickness: CGFloat?
|
|
100
|
+
var verticalMargin: CGFloat?
|
|
101
|
+
|
|
102
|
+
init(dictionary: [String: Any]) {
|
|
103
|
+
color = EditorTheme.color(from: dictionary["color"])
|
|
104
|
+
thickness = EditorTheme.cgFloat(dictionary["thickness"])
|
|
105
|
+
verticalMargin = EditorTheme.cgFloat(dictionary["verticalMargin"])
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
struct EditorBlockquoteTheme {
|
|
110
|
+
var text: EditorTextStyle?
|
|
111
|
+
var indent: CGFloat?
|
|
112
|
+
var borderColor: UIColor?
|
|
113
|
+
var borderWidth: CGFloat?
|
|
114
|
+
var markerGap: CGFloat?
|
|
115
|
+
|
|
116
|
+
init(dictionary: [String: Any]) {
|
|
117
|
+
if let text = dictionary["text"] as? [String: Any] {
|
|
118
|
+
self.text = EditorTextStyle(dictionary: text)
|
|
119
|
+
}
|
|
120
|
+
indent = EditorTheme.cgFloat(dictionary["indent"])
|
|
121
|
+
borderColor = EditorTheme.color(from: dictionary["borderColor"])
|
|
122
|
+
borderWidth = EditorTheme.cgFloat(dictionary["borderWidth"])
|
|
123
|
+
markerGap = EditorTheme.cgFloat(dictionary["markerGap"])
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
struct EditorCodeBlockTheme {
|
|
128
|
+
var text: EditorTextStyle?
|
|
129
|
+
var backgroundColor: UIColor?
|
|
130
|
+
var borderRadius: CGFloat?
|
|
131
|
+
var paddingHorizontal: CGFloat?
|
|
132
|
+
var paddingVertical: CGFloat?
|
|
133
|
+
|
|
134
|
+
init(dictionary: [String: Any]) {
|
|
135
|
+
if let text = dictionary["text"] as? [String: Any] {
|
|
136
|
+
self.text = EditorTextStyle(dictionary: text)
|
|
137
|
+
}
|
|
138
|
+
backgroundColor = EditorTheme.color(from: dictionary["backgroundColor"])
|
|
139
|
+
borderRadius = EditorTheme.cgFloat(dictionary["borderRadius"])
|
|
140
|
+
paddingHorizontal = EditorTheme.cgFloat(dictionary["paddingHorizontal"])
|
|
141
|
+
paddingVertical = EditorTheme.cgFloat(dictionary["paddingVertical"])
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
struct EditorLinkTheme {
|
|
146
|
+
var fontFamily: String?
|
|
147
|
+
var fontSize: CGFloat?
|
|
148
|
+
var fontWeight: String?
|
|
149
|
+
var fontStyle: String?
|
|
150
|
+
var color: UIColor?
|
|
151
|
+
var backgroundColor: UIColor?
|
|
152
|
+
var underline: Bool?
|
|
153
|
+
|
|
154
|
+
init(dictionary: [String: Any]) {
|
|
155
|
+
fontFamily = dictionary["fontFamily"] as? String
|
|
156
|
+
fontSize = EditorTheme.cgFloat(dictionary["fontSize"])
|
|
157
|
+
fontWeight = dictionary["fontWeight"] as? String
|
|
158
|
+
fontStyle = dictionary["fontStyle"] as? String
|
|
159
|
+
color = EditorTheme.color(from: dictionary["color"])
|
|
160
|
+
backgroundColor = EditorTheme.color(from: dictionary["backgroundColor"])
|
|
161
|
+
underline = dictionary["underline"] as? Bool
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
func resolvedFont(fallback: UIFont) -> UIFont {
|
|
165
|
+
EditorTextStyle(
|
|
166
|
+
fontFamily: fontFamily,
|
|
167
|
+
fontSize: fontSize,
|
|
168
|
+
fontWeight: fontWeight,
|
|
169
|
+
fontStyle: fontStyle
|
|
170
|
+
).resolvedFont(fallback: fallback)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
struct EditorMentionTheme {
|
|
175
|
+
var textColor: UIColor?
|
|
176
|
+
var backgroundColor: UIColor?
|
|
177
|
+
var borderColor: UIColor?
|
|
178
|
+
var borderWidth: CGFloat?
|
|
179
|
+
var borderRadius: CGFloat?
|
|
180
|
+
var fontWeight: String?
|
|
181
|
+
var popoverBackgroundColor: UIColor?
|
|
182
|
+
var popoverBorderColor: UIColor?
|
|
183
|
+
var popoverBorderWidth: CGFloat?
|
|
184
|
+
var popoverBorderRadius: CGFloat?
|
|
185
|
+
var popoverShadowColor: UIColor?
|
|
186
|
+
var optionTextColor: UIColor?
|
|
187
|
+
var optionSecondaryTextColor: UIColor?
|
|
188
|
+
var optionHighlightedBackgroundColor: UIColor?
|
|
189
|
+
var optionHighlightedTextColor: UIColor?
|
|
190
|
+
|
|
191
|
+
func merged(with override: EditorMentionTheme?) -> EditorMentionTheme {
|
|
192
|
+
guard let override else { return self }
|
|
193
|
+
var merged = self
|
|
194
|
+
merged.textColor = override.textColor ?? merged.textColor
|
|
195
|
+
merged.backgroundColor = override.backgroundColor ?? merged.backgroundColor
|
|
196
|
+
merged.borderColor = override.borderColor ?? merged.borderColor
|
|
197
|
+
merged.borderWidth = override.borderWidth ?? merged.borderWidth
|
|
198
|
+
merged.borderRadius = override.borderRadius ?? merged.borderRadius
|
|
199
|
+
merged.fontWeight = override.fontWeight ?? merged.fontWeight
|
|
200
|
+
merged.popoverBackgroundColor =
|
|
201
|
+
override.popoverBackgroundColor ?? merged.popoverBackgroundColor
|
|
202
|
+
merged.popoverBorderColor = override.popoverBorderColor ?? merged.popoverBorderColor
|
|
203
|
+
merged.popoverBorderWidth = override.popoverBorderWidth ?? merged.popoverBorderWidth
|
|
204
|
+
merged.popoverBorderRadius = override.popoverBorderRadius ?? merged.popoverBorderRadius
|
|
205
|
+
merged.popoverShadowColor = override.popoverShadowColor ?? merged.popoverShadowColor
|
|
206
|
+
merged.optionTextColor = override.optionTextColor ?? merged.optionTextColor
|
|
207
|
+
merged.optionSecondaryTextColor =
|
|
208
|
+
override.optionSecondaryTextColor ?? merged.optionSecondaryTextColor
|
|
209
|
+
merged.optionHighlightedBackgroundColor =
|
|
210
|
+
override.optionHighlightedBackgroundColor ?? merged.optionHighlightedBackgroundColor
|
|
211
|
+
merged.optionHighlightedTextColor =
|
|
212
|
+
override.optionHighlightedTextColor ?? merged.optionHighlightedTextColor
|
|
213
|
+
return merged
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
init(dictionary: [String: Any]) {
|
|
217
|
+
textColor = EditorTheme.color(from: dictionary["textColor"])
|
|
218
|
+
backgroundColor = EditorTheme.color(from: dictionary["backgroundColor"])
|
|
219
|
+
borderColor = EditorTheme.color(from: dictionary["borderColor"])
|
|
220
|
+
borderWidth = EditorTheme.cgFloat(dictionary["borderWidth"])
|
|
221
|
+
borderRadius = EditorTheme.cgFloat(dictionary["borderRadius"])
|
|
222
|
+
fontWeight = dictionary["fontWeight"] as? String
|
|
223
|
+
popoverBackgroundColor = EditorTheme.color(from: dictionary["popoverBackgroundColor"])
|
|
224
|
+
popoverBorderColor = EditorTheme.color(from: dictionary["popoverBorderColor"])
|
|
225
|
+
popoverBorderWidth = EditorTheme.cgFloat(dictionary["popoverBorderWidth"])
|
|
226
|
+
popoverBorderRadius = EditorTheme.cgFloat(dictionary["popoverBorderRadius"])
|
|
227
|
+
popoverShadowColor = EditorTheme.color(from: dictionary["popoverShadowColor"])
|
|
228
|
+
optionTextColor = EditorTheme.color(from: dictionary["optionTextColor"])
|
|
229
|
+
optionSecondaryTextColor = EditorTheme.color(from: dictionary["optionSecondaryTextColor"])
|
|
230
|
+
optionHighlightedBackgroundColor = EditorTheme.color(from: dictionary["optionHighlightedBackgroundColor"])
|
|
231
|
+
optionHighlightedTextColor = EditorTheme.color(from: dictionary["optionHighlightedTextColor"])
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
enum EditorToolbarAppearance: String {
|
|
236
|
+
case custom
|
|
237
|
+
case native
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
struct EditorToolbarTheme {
|
|
241
|
+
var appearance: EditorToolbarAppearance?
|
|
242
|
+
var height: CGFloat?
|
|
243
|
+
var backgroundColor: UIColor?
|
|
244
|
+
var borderColor: UIColor?
|
|
245
|
+
var borderWidth: CGFloat?
|
|
246
|
+
var borderRadius: CGFloat?
|
|
247
|
+
var marginTop: CGFloat?
|
|
248
|
+
var showTopBorder: Bool?
|
|
249
|
+
var keyboardOffset: CGFloat?
|
|
250
|
+
var horizontalInset: CGFloat?
|
|
251
|
+
var separatorColor: UIColor?
|
|
252
|
+
var buttonColor: UIColor?
|
|
253
|
+
var buttonActiveColor: UIColor?
|
|
254
|
+
var buttonDisabledColor: UIColor?
|
|
255
|
+
var buttonActiveBackgroundColor: UIColor?
|
|
256
|
+
var buttonBorderRadius: CGFloat?
|
|
257
|
+
|
|
258
|
+
init(dictionary: [String: Any]) {
|
|
259
|
+
appearance = (dictionary["appearance"] as? String).flatMap(EditorToolbarAppearance.init(rawValue:))
|
|
260
|
+
height = EditorTheme.cgFloat(dictionary["height"])
|
|
261
|
+
backgroundColor = EditorTheme.color(from: dictionary["backgroundColor"])
|
|
262
|
+
borderColor = EditorTheme.color(from: dictionary["borderColor"])
|
|
263
|
+
borderWidth = EditorTheme.cgFloat(dictionary["borderWidth"])
|
|
264
|
+
borderRadius = EditorTheme.cgFloat(dictionary["borderRadius"])
|
|
265
|
+
marginTop = EditorTheme.cgFloat(dictionary["marginTop"])
|
|
266
|
+
showTopBorder = dictionary["showTopBorder"] as? Bool
|
|
267
|
+
keyboardOffset = EditorTheme.cgFloat(dictionary["keyboardOffset"])
|
|
268
|
+
horizontalInset = EditorTheme.cgFloat(dictionary["horizontalInset"])
|
|
269
|
+
separatorColor = EditorTheme.color(from: dictionary["separatorColor"])
|
|
270
|
+
buttonColor = EditorTheme.color(from: dictionary["buttonColor"])
|
|
271
|
+
buttonActiveColor = EditorTheme.color(from: dictionary["buttonActiveColor"])
|
|
272
|
+
buttonDisabledColor = EditorTheme.color(from: dictionary["buttonDisabledColor"])
|
|
273
|
+
buttonActiveBackgroundColor = EditorTheme.color(from: dictionary["buttonActiveBackgroundColor"])
|
|
274
|
+
buttonBorderRadius = EditorTheme.cgFloat(dictionary["buttonBorderRadius"])
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
var resolvedKeyboardOffset: CGFloat {
|
|
278
|
+
keyboardOffset ?? (appearance == .native ? 6 : 0)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
var resolvedHorizontalInset: CGFloat {
|
|
282
|
+
horizontalInset ?? (appearance == .native ? 10 : 0)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
var resolvedBorderRadius: CGFloat {
|
|
286
|
+
borderRadius ?? (appearance == .native ? 20 : 0)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
var resolvedBorderWidth: CGFloat {
|
|
290
|
+
borderWidth ?? (appearance == .native ? 0 : 0.5)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
var resolvedButtonBorderRadius: CGFloat {
|
|
294
|
+
buttonBorderRadius ?? (appearance == .native ? 10 : 8)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
struct EditorContentInsets {
|
|
299
|
+
var top: CGFloat?
|
|
300
|
+
var right: CGFloat?
|
|
301
|
+
var bottom: CGFloat?
|
|
302
|
+
var left: CGFloat?
|
|
303
|
+
|
|
304
|
+
init(dictionary: [String: Any]) {
|
|
305
|
+
top = EditorTheme.cgFloat(dictionary["top"])
|
|
306
|
+
right = EditorTheme.cgFloat(dictionary["right"])
|
|
307
|
+
bottom = EditorTheme.cgFloat(dictionary["bottom"])
|
|
308
|
+
left = EditorTheme.cgFloat(dictionary["left"])
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
struct EditorTheme {
|
|
313
|
+
var text: EditorTextStyle?
|
|
314
|
+
var paragraph: EditorTextStyle?
|
|
315
|
+
var blockquote: EditorBlockquoteTheme?
|
|
316
|
+
var codeBlock: EditorCodeBlockTheme?
|
|
317
|
+
var headings: [String: EditorTextStyle] = [:]
|
|
318
|
+
var list: EditorListTheme?
|
|
319
|
+
var horizontalRule: EditorHorizontalRuleTheme?
|
|
320
|
+
var mentions: EditorMentionTheme?
|
|
321
|
+
var links: EditorLinkTheme?
|
|
322
|
+
var toolbar: EditorToolbarTheme?
|
|
323
|
+
var placeholderColor: UIColor?
|
|
324
|
+
var backgroundColor: UIColor?
|
|
325
|
+
var borderRadius: CGFloat?
|
|
326
|
+
var contentInsets: EditorContentInsets?
|
|
327
|
+
|
|
328
|
+
static func from(json: String?) -> EditorTheme? {
|
|
329
|
+
guard let json, !json.isEmpty,
|
|
330
|
+
let data = json.data(using: .utf8),
|
|
331
|
+
let raw = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
332
|
+
else {
|
|
333
|
+
return nil
|
|
334
|
+
}
|
|
335
|
+
return EditorTheme(dictionary: raw)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
init(dictionary: [String: Any]) {
|
|
339
|
+
if let text = dictionary["text"] as? [String: Any] {
|
|
340
|
+
self.text = EditorTextStyle(dictionary: text)
|
|
341
|
+
}
|
|
342
|
+
if let paragraph = dictionary["paragraph"] as? [String: Any] {
|
|
343
|
+
self.paragraph = EditorTextStyle(dictionary: paragraph)
|
|
344
|
+
}
|
|
345
|
+
if let blockquote = dictionary["blockquote"] as? [String: Any] {
|
|
346
|
+
self.blockquote = EditorBlockquoteTheme(dictionary: blockquote)
|
|
347
|
+
}
|
|
348
|
+
if let codeBlock = dictionary["codeBlock"] as? [String: Any] {
|
|
349
|
+
self.codeBlock = EditorCodeBlockTheme(dictionary: codeBlock)
|
|
350
|
+
}
|
|
351
|
+
if let headings = dictionary["headings"] as? [String: Any] {
|
|
352
|
+
for level in ["h1", "h2", "h3", "h4", "h5", "h6"] {
|
|
353
|
+
if let style = headings[level] as? [String: Any] {
|
|
354
|
+
self.headings[level] = EditorTextStyle(dictionary: style)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if let list = dictionary["list"] as? [String: Any] {
|
|
359
|
+
self.list = EditorListTheme(dictionary: list)
|
|
360
|
+
}
|
|
361
|
+
if let horizontalRule = dictionary["horizontalRule"] as? [String: Any] {
|
|
362
|
+
self.horizontalRule = EditorHorizontalRuleTheme(dictionary: horizontalRule)
|
|
363
|
+
}
|
|
364
|
+
if let mentions = dictionary["mentions"] as? [String: Any] {
|
|
365
|
+
self.mentions = EditorMentionTheme(dictionary: mentions)
|
|
366
|
+
}
|
|
367
|
+
if let links = dictionary["links"] as? [String: Any] {
|
|
368
|
+
self.links = EditorLinkTheme(dictionary: links)
|
|
369
|
+
}
|
|
370
|
+
if let toolbar = dictionary["toolbar"] as? [String: Any] {
|
|
371
|
+
self.toolbar = EditorToolbarTheme(dictionary: toolbar)
|
|
372
|
+
}
|
|
373
|
+
placeholderColor = EditorTheme.color(from: dictionary["placeholderColor"])
|
|
374
|
+
backgroundColor = EditorTheme.color(from: dictionary["backgroundColor"])
|
|
375
|
+
borderRadius = EditorTheme.cgFloat(dictionary["borderRadius"])
|
|
376
|
+
if let contentInsets = dictionary["contentInsets"] as? [String: Any] {
|
|
377
|
+
self.contentInsets = EditorContentInsets(dictionary: contentInsets)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
func effectiveTextStyle(for nodeType: String, inBlockquote: Bool = false) -> EditorTextStyle {
|
|
382
|
+
var style = text ?? EditorTextStyle()
|
|
383
|
+
style = style.merged(with: inBlockquote ? blockquote?.text : nil)
|
|
384
|
+
if nodeType == "paragraph" {
|
|
385
|
+
style = style.merged(with: paragraph)
|
|
386
|
+
if paragraph?.lineHeight == nil {
|
|
387
|
+
style.lineHeight = nil
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if nodeType == "codeBlock" {
|
|
391
|
+
style = style.merged(with: codeBlock?.text)
|
|
392
|
+
}
|
|
393
|
+
style = style.merged(with: headings[nodeType])
|
|
394
|
+
return style
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
static func cgFloat(_ value: Any?) -> CGFloat? {
|
|
398
|
+
guard let number = value as? NSNumber else { return nil }
|
|
399
|
+
return CGFloat(truncating: number)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
static func fontWeight(from value: String) -> UIFont.Weight {
|
|
403
|
+
switch value {
|
|
404
|
+
case "100": return .ultraLight
|
|
405
|
+
case "200": return .thin
|
|
406
|
+
case "300": return .light
|
|
407
|
+
case "500": return .medium
|
|
408
|
+
case "600": return .semibold
|
|
409
|
+
case "700", "bold": return .bold
|
|
410
|
+
case "800": return .heavy
|
|
411
|
+
case "900": return .black
|
|
412
|
+
default: return .regular
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
static func shouldApplyBoldTrait(_ value: String?) -> Bool {
|
|
417
|
+
guard let value else { return false }
|
|
418
|
+
return value == "bold" || Int(value).map { $0 >= 600 } == true
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
static func color(from value: Any?) -> UIColor? {
|
|
422
|
+
guard let raw = value as? String else { return nil }
|
|
423
|
+
let string = raw.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
|
424
|
+
|
|
425
|
+
if let hexColor = colorFromHex(string) {
|
|
426
|
+
return hexColor
|
|
427
|
+
}
|
|
428
|
+
if let rgbColor = colorFromRGBFunction(string) {
|
|
429
|
+
return rgbColor
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
switch string {
|
|
433
|
+
case "black": return .black
|
|
434
|
+
case "white": return .white
|
|
435
|
+
case "red": return .red
|
|
436
|
+
case "green": return .green
|
|
437
|
+
case "blue": return .blue
|
|
438
|
+
case "gray", "grey": return .gray
|
|
439
|
+
case "clear", "transparent": return .clear
|
|
440
|
+
default: return nil
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
private static func colorFromHex(_ string: String) -> UIColor? {
|
|
445
|
+
guard string.hasPrefix("#") else { return nil }
|
|
446
|
+
let hex = String(string.dropFirst())
|
|
447
|
+
|
|
448
|
+
switch hex.count {
|
|
449
|
+
case 3:
|
|
450
|
+
let chars = Array(hex)
|
|
451
|
+
return UIColor(
|
|
452
|
+
red: component(String(repeating: String(chars[0]), count: 2)),
|
|
453
|
+
green: component(String(repeating: String(chars[1]), count: 2)),
|
|
454
|
+
blue: component(String(repeating: String(chars[2]), count: 2)),
|
|
455
|
+
alpha: 1
|
|
456
|
+
)
|
|
457
|
+
case 4:
|
|
458
|
+
let chars = Array(hex)
|
|
459
|
+
return UIColor(
|
|
460
|
+
red: component(String(repeating: String(chars[0]), count: 2)),
|
|
461
|
+
green: component(String(repeating: String(chars[1]), count: 2)),
|
|
462
|
+
blue: component(String(repeating: String(chars[2]), count: 2)),
|
|
463
|
+
alpha: component(String(repeating: String(chars[3]), count: 2))
|
|
464
|
+
)
|
|
465
|
+
case 6:
|
|
466
|
+
return UIColor(
|
|
467
|
+
red: component(String(hex.prefix(2))),
|
|
468
|
+
green: component(String(hex.dropFirst(2).prefix(2))),
|
|
469
|
+
blue: component(String(hex.dropFirst(4).prefix(2))),
|
|
470
|
+
alpha: 1
|
|
471
|
+
)
|
|
472
|
+
case 8:
|
|
473
|
+
return UIColor(
|
|
474
|
+
red: component(String(hex.prefix(2))),
|
|
475
|
+
green: component(String(hex.dropFirst(2).prefix(2))),
|
|
476
|
+
blue: component(String(hex.dropFirst(4).prefix(2))),
|
|
477
|
+
alpha: component(String(hex.dropFirst(6).prefix(2)))
|
|
478
|
+
)
|
|
479
|
+
default:
|
|
480
|
+
return nil
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
private static func colorFromRGBFunction(_ string: String) -> UIColor? {
|
|
485
|
+
let isRGBA = string.hasPrefix("rgba(") && string.hasSuffix(")")
|
|
486
|
+
let isRGB = string.hasPrefix("rgb(") && string.hasSuffix(")")
|
|
487
|
+
guard isRGBA || isRGB else { return nil }
|
|
488
|
+
|
|
489
|
+
let start = string.index(string.startIndex, offsetBy: isRGBA ? 5 : 4)
|
|
490
|
+
let end = string.index(before: string.endIndex)
|
|
491
|
+
let parts = string[start..<end]
|
|
492
|
+
.split(separator: ",")
|
|
493
|
+
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
|
494
|
+
|
|
495
|
+
guard parts.count == (isRGBA ? 4 : 3),
|
|
496
|
+
let red = Double(parts[0]),
|
|
497
|
+
let green = Double(parts[1]),
|
|
498
|
+
let blue = Double(parts[2])
|
|
499
|
+
else {
|
|
500
|
+
return nil
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
let alpha = isRGBA ? (Double(parts[3]) ?? 1) : 1
|
|
504
|
+
return UIColor(
|
|
505
|
+
red: red / 255,
|
|
506
|
+
green: green / 255,
|
|
507
|
+
blue: blue / 255,
|
|
508
|
+
alpha: alpha
|
|
509
|
+
)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
private static func component(_ hex: String) -> CGFloat {
|
|
513
|
+
CGFloat(Int(hex, radix: 16) ?? 0) / 255
|
|
514
|
+
}
|
|
515
|
+
}
|