Skip to content

Commit 2146bdf

Browse files
authored
Merge pull request #2 from contentstack/feature/supercharge-rte
Feature/supercharge rte
2 parents a526bb6 + 0f77413 commit 2146bdf

25 files changed

Lines changed: 2363 additions & 128 deletions

.slather.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ scheme: ContentstackUtils-Package
55
ignore:
66
- "Tests/*"
77
- "Sources/Kanna/*"
8+
- "Sources/ContentstackUtils/Decodable.*"

.talismanrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
fileignoreconfig:
2+
- filename: README.md
3+
allowed_patterns: [API_KEY, DELIVERY_TOKEN, ENVIRONMENT]
4+
- filename: ContentstackUtils.xcodeproj/project.pbxproj
5+
checksum: 5b62a76622fed7df9967d8646467bd2f04c221df2c95eddce7541fd8ab448078

ContentstackUtils.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'ContentstackUtils'
3-
s.version = '1.1.1'
3+
s.version = '1.1.2'
44
s.summary = 'Contentstack is a headless CMS with an API-first approach that puts content at the centre.'
55

66
s.description = <<-DESC

ContentstackUtils.xcodeproj/project.pbxproj

Lines changed: 82 additions & 8 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 98 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -82,54 +82,84 @@ To render embedded items on the front-end, create a class implementing Option pr
8282
import Foundation
8383
import ContentstackUtils
8484
class CustomRenderOption: Option {
85-
var entry: EntryEmbedable
86-
87-
init(entry: EntryEmbedable) {
88-
self.entry = entry
89-
}
85+
86+
override func renderMark(markType: MarkType, text: String) -> String {
87+
switch markType {
88+
case .bold:
89+
return "<b>\(text)</b>"
90+
default:
91+
return super.renderMark(markType: markType, text: text)
92+
}
93+
}
94+
95+
override func renderNode(nodeType: String, node: Node, next: (([Node]) -> String)) -> String {
96+
switch nodeType {
97+
case "p":
98+
return "<p class='class-id'>\(next(node.children))</p>"
99+
case "h1":
100+
return "<h1 class='class-id'>\(next(node.children))</h1>"
101+
default:
102+
return super.renderNode(nodeType: nodeType, node: node, next: next)
103+
}
104+
}
90105
91-
func renderOptions(embeddedObject: EmbeddedObject, metadata: Metadata) -> String? {
92-
var result = ""
93-
switch metadata.styleType {
94-
case .block:
95-
if metadata.contentTypeUid == 'product' {
96-
result = """
97-
<div>
98-
<h2 >\(embeddedObject.title)</h2>
99-
<img src=\(embeddedObject.product_image.url) alt=\(embeddedObject.product_image.title)/>
100-
<p>\(embeddedObject.price)</p>
101-
</div>
102-
"""
103-
}else {
104-
result = """
105-
<div>
106-
<h2>\(entry.title)</h2>
107-
<p>\(entry.description)</p>
108-
</div>
109-
"""
110-
}
111-
case .inline:
112-
result = "<span><b>\(embeddedObject.title)</b> - \(embeddedObject.description)</span>"
113-
case .link:
114-
result = "<a href=\(metadata.attributes.href)>\(metadata.text)</a>"
115-
case .display:
116-
result = "<img src=\(metadata.attributes.src) alt=\(metadata.alt) />"
117-
case .download:
118-
result = "<a href=\(metadata.attributes.href)>\(metadata.text)</a>"
119-
}
120-
return result
121-
}
106+
func renderOptions(embeddedObject: EmbeddedObject, metadata: Metadata) -> String? {
107+
switch metadata.styleType {
108+
case .block:
109+
if metadata.contentTypeUid == "product" {
110+
if let product = embeddedObject as? Product {
111+
return """
112+
<div>
113+
<h2 >\(product.title)</h2>
114+
<img src=\(product.product_image.url) alt=\(product.product_image.title)/>
115+
<p>\(product.price)</p>
116+
</div>
117+
"""
118+
}
119+
}else {
120+
if let entry = embeddedObject as? Entry {
121+
return """
122+
<div>
123+
<h2>\(entry.title)</h2>
124+
<p>\(entry.description)</p>
125+
</div>
126+
"""
127+
}
128+
}
129+
default:
130+
return super.renderOptions(embeddedObject: embeddedObject, metadata: metadata)
131+
}
122132
}
123133
```
124134
125135
## Basic Queries
126136
Contentstack Utils SDK lets you interact with the Content Delivery APIs and retrieve embedded items from the RTE field of an entry.
127137
128138
### Fetch Embedded Item(s) from a Single Entry
139+
#### Render HTML RTE Embedded object
140+
To get an embedded items of a single entry, you need to provide the stack API key, environment name, delivery token, content type and entry UID. Then, use the `ContentstackUtils.render` functions as shown below:
141+
```swift
142+
import ContentstackUtils
143+
144+
let stack:Stack = Contentstack.stack(apiKey: API_KEY, deliveryToken: DELIVERY_TOKEN, environment: ENVIRONMENT)
129145
130-
To get an embedded items of a single entry, you need to provide the stack API key, environment name, delivery token, content type and entry UID. Then, use the Contentstack.Utils.render functions as shown below:
146+
stack.contentType(uid: contentTypeUID)
147+
.entry(uid: entryUID)
148+
.include(.embeddedItems)
149+
.fetch { (result: Result<EntryModel, Error>, response: ResponseType) in
150+
switch result {
151+
case .success(let model):
152+
ContentstackUtils.render(content: model.richTextContent, Option(entry: model))
153+
case .failure(let error):
154+
//Error Message
155+
}
156+
}
131157
```
132-
import ContentstackSwift
158+
159+
#### Render Supercharged RTE contents
160+
To get a single entry, you need to provide the stack API key, environment name, delivery token, content type and entry UID. Then, use `ContentstackUtils.jsonToHtml` function as shown below:
161+
```swift
162+
import ContentstackUtils
133163
134164
let stack:Stack = Contentstack.stack(apiKey: API_KEY, deliveryToken: DELIVERY_TOKEN, environment: ENVIRONMENT)
135165
@@ -139,16 +169,19 @@ stack.contentType(uid: contentTypeUID)
139169
.fetch { (result: Result<EntryModel, Error>, response: ResponseType) in
140170
switch result {
141171
case .success(let model):
142-
Contentstack.Utils.render(content: model.richTextContent, DefaultRender(entry: model))
172+
ContentstackUtils.jsonToHtml(content: model.richTextContent, Option(entry: model))
143173
case .failure(let error):
144174
//Error Message
145175
}
146176
}
147177
```
178+
> Node: Supercharged RTE also supports Embedded items to get all embedded items while fetching entry use `includeEmbeddedItems` function.
179+
148180
### Fetch Embedded Item(s) from Multiple Entries
149-
To get embedded items from multiple entries, you need to provide the stack API key, environment name, delivery token, and content type UID. Then, use the Contentstack.Utils.render functions as shown below:
181+
#### Render HTML RTE Embedded object
182+
To get embedded items from multiple entries, you need to provide the stack API key, environment name, delivery token, and content type UID. Then, use the `ContentstackUtils.render` functions as shown below:
150183
```
151-
import ContentstackSwift
184+
import ContentstackUtils
152185
153186
let stack = Contentstack.stack(apiKey: apiKey,
154187
deliveryToken: deliveryToken,
@@ -162,10 +195,33 @@ stack.contentType(uid: contentTypeUID)
162195
switch result {
163196
case .success(let contentstackResponse):
164197
for item in contentstackResponse.items {
165-
Contentstack.Utils.render(content: item.richTextContent, CustomRenderOption(entry: item))
198+
ContentstackUtils.render(content: item.richTextContent, CustomRenderOption(entry: item))
166199
}
167200
case .failure(let error):
168201
//Error Message
169202
}
170203
}
171204
```
205+
206+
#### Render Supercharged RTE contents
207+
To get a Multiple entry, you need to provide the stack API key, environment name, delivery token, and content type UID. Then, use `Contentstack.Utils.jsonToHtml` function as shown below:
208+
```swift
209+
import ContentstackUtils
210+
211+
let stack:Stack = Contentstack.stack(apiKey: API_KEY, deliveryToken: DELIVERY_TOKEN, environment: ENVIRONMENT)
212+
213+
stack.contentType(uid: contentTypeUID)
214+
.entry()
215+
.query()
216+
.include(.embeddedItems)
217+
.find { (result: Result<EntryModel, Error>, response: ResponseType) in
218+
switch result {
219+
case .success(let model):
220+
for item in contentstackResponse.items {
221+
ContentstackUtils.jsonToHtml(content: item.richTextContent, CustomRenderOption(entry: item))
222+
}
223+
case .failure(let error):
224+
//Error Message
225+
}
226+
}
227+
```

Sources/ContentstackUtils/ContentstackUtils.swift

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ public struct ContentstackUtils {
1010
.findEmbeddedObject { (model) in
1111
if let outerHTML = model.outerHTML {
1212
var replaceString = ""
13-
if let embeddedObject = findObject(model, entry: option.entry) {
14-
if let string = option.renderOptions(
13+
if let entry = option.entry,
14+
let embeddedObject = findObject(model, entry: entry) {
15+
if let string = option.renderItem(
1516
embeddedObject: embeddedObject,
1617
metadata: model) {
1718
replaceString = string
@@ -38,7 +39,74 @@ public struct ContentstackUtils {
3839
throw error
3940
}
4041
}
42+
43+
44+
public static func jsonToHtml(node documents: [Node], _ option: Option = Option()) -> [String] {
45+
var resultContents: [String] = []
46+
documents.forEach { (document) in
47+
resultContents.append(jsonToHtml(node: document, option))
48+
}
49+
return resultContents
50+
}
4151

52+
public static func jsonToHtml(node document: Node, _ option: Option = Option()) -> String {
53+
return nodeChildrenToHtml(children: document.children, option)
54+
}
55+
56+
static private func nodeChildrenToHtml(children nodes:[Node], _ option: Option) -> String {
57+
nodes.map{ (node) -> String in
58+
return nodeToHtml(node, option)
59+
}.joined()
60+
}
61+
62+
static private func nodeToHtml(_ node: Node, _ option: Option) -> String {
63+
switch node.type {
64+
case NodeType.text.rawValue:
65+
return textNodeToHtml(node as! TextNode, option)
66+
case NodeType.reference.rawValue:
67+
return referenceToHtml(node, option)
68+
default:
69+
return option.renderNode(nodeType: node.type, node: node) { (children) -> String in
70+
return nodeChildrenToHtml(children: children, option)
71+
}
72+
}
73+
}
74+
75+
static private func textNodeToHtml(_ textNode: TextNode, _ option: Option) -> String {
76+
var text = textNode.text
77+
if (textNode.superscript) {
78+
text = option.renderMark(markType: .superscript, text: text)
79+
}
80+
if (textNode.subscript) {
81+
text = option.renderMark(markType: .subscript, text: text)
82+
}
83+
if (textNode.inlineCode) {
84+
text = option.renderMark(markType: .inlineCode, text: text)
85+
}
86+
if (textNode.strikethrough) {
87+
text = option.renderMark(markType: .strikethrough, text: text)
88+
}
89+
if (textNode.underline) {
90+
text = option.renderMark(markType: .underline, text: text)
91+
}
92+
if (textNode.italic) {
93+
text = option.renderMark(markType: .italic, text: text)
94+
}
95+
if (textNode.bold) {
96+
text = option.renderMark(markType: .bold, text: text)
97+
}
98+
return text
99+
}
100+
101+
static private func referenceToHtml(_ node: Node, _ option: Option) -> String {
102+
let metadata = Metadata(nodeAttribute: node.attrs, text: node.children.count > 0 ? (node.children.first as? TextNode)?.text : "")
103+
if let entry = option.entry,
104+
let embeddedObject = findObject(metadata, entry: entry) {
105+
return option.renderItem(embeddedObject: embeddedObject, metadata: metadata) ?? ""
106+
}
107+
return ""
108+
}
109+
42110
static private func findObject(_ model: Metadata, entry: EntryEmbedable) -> EmbeddedObject? {
43111
if let items = entry.embeddedItems, let uid = model.itemUid, let contentTypeUID = model.contentTypeUid {
44112
for (_, value) in items {
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//
2+
// Decodable.swift
3+
// ContentstackUtils
4+
//
5+
// Created by Uttam Ukkoji on 04/06/21.
6+
//
7+
8+
internal struct JSONCodingKeys: CodingKey {
9+
internal var stringValue: String
10+
internal init?(stringValue: String) {
11+
self.stringValue = stringValue
12+
}
13+
internal var intValue: Int?
14+
internal init?(intValue: Int) {
15+
self.init(stringValue: "\(intValue)")
16+
self.intValue = intValue
17+
}
18+
}
19+
20+
internal extension KeyedDecodingContainer {
21+
22+
func decode(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> [String: Any] {
23+
let container = try self.nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key)
24+
return try container.decode(type)
25+
}
26+
27+
func decodeIfPresent(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> [String: Any]? {
28+
guard contains(key) else { return nil }
29+
guard try decodeNil(forKey: key) == false else { return nil }
30+
return try decode(type, forKey: key)
31+
}
32+
33+
func decode(_ type: Array<Any>.Type, forKey key: K) throws -> [Any] {
34+
var container = try self.nestedUnkeyedContainer(forKey: key)
35+
return try container.decode(type)
36+
}
37+
38+
func decodeIfPresent(_ type: Array<Any>.Type, forKey key: K) throws -> [Any]? {
39+
guard contains(key) else { return nil }
40+
guard try decodeNil(forKey: key) == false else { return nil }
41+
return try decode(type, forKey: key)
42+
}
43+
44+
func decode(_ type: Dictionary<String, Any>.Type) throws -> [String: Any] {
45+
var dictionary = [String: Any]()
46+
for key in allKeys {
47+
if let boolValue = try? decode(Bool.self, forKey: key) {
48+
dictionary[key.stringValue] = boolValue
49+
} else if let stringValue = try? decode(String.self, forKey: key) {
50+
dictionary[key.stringValue] = stringValue
51+
} else if let intValue = try? decode(Int.self, forKey: key) {
52+
dictionary[key.stringValue] = intValue
53+
} else if let doubleValue = try? decode(Double.self, forKey: key) {
54+
dictionary[key.stringValue] = doubleValue
55+
} else if let nestedDictionary = try? decode(Dictionary<String, Any>.self, forKey: key) {
56+
dictionary[key.stringValue] = nestedDictionary
57+
} else if let nestedArray = try? decode(Array<Any>.self, forKey: key) {
58+
dictionary[key.stringValue] = nestedArray
59+
}
60+
}
61+
return dictionary
62+
}
63+
}
64+
65+
internal extension UnkeyedDecodingContainer {
66+
67+
mutating func decode(_ type: Array<Any>.Type) throws -> [Any] {
68+
var array: [Any] = []
69+
while isAtEnd == false {
70+
if try decodeNil() {
71+
continue
72+
} else if let value = try? decode(Bool.self) {
73+
array.append(value)
74+
} else if let value = try? decode(Int.self) {
75+
array.append(value)
76+
} else if let value = try? decode(Double.self) {
77+
array.append(value)
78+
} else if let value = try? decode(String.self) {
79+
array.append(value)
80+
} else if let nestedDictionary = try? decode(Dictionary<String, Any>.self) {
81+
array.append(nestedDictionary)
82+
} else if let nestedArray = try? decode(Array<Any>.self) {
83+
array.append(nestedArray)
84+
}
85+
}
86+
return array
87+
}
88+
89+
mutating func decode(_ type: [String: Any].Type) throws -> [String: Any] {
90+
let nestedContainer = try self.nestedContainer(keyedBy: JSONCodingKeys.self)
91+
return try nestedContainer.decode(type)
92+
}
93+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//
2+
// MarkType.swift
3+
// ContentstackUtils
4+
//
5+
// Created by Uttam Ukkoji on 03/06/21.
6+
//
7+
8+
public enum MarkType: String {
9+
case bold, italic, underline, strikethrough, inlineCode, `subscript`, superscript
10+
}

0 commit comments

Comments
 (0)