wcc-contentful 1.4.0.rc3 → 1.5.0.rc1
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.
- checksums.yaml +4 -4
- data/README.md +56 -4
- data/lib/tasks/rewrite_fixtures.rake +61 -0
- data/lib/wcc/contentful/action_view_rich_text_renderer.rb +12 -0
- data/lib/wcc/contentful/configuration.rb +13 -0
- data/lib/wcc/contentful/entry_locale_transformer.rb +33 -32
- data/lib/wcc/contentful/middleware/store/caching_middleware.rb +4 -2
- data/lib/wcc/contentful/model_api.rb +1 -1
- data/lib/wcc/contentful/model_builder.rb +1 -1
- data/lib/wcc/contentful/rich_text/node.rb +30 -6
- data/lib/wcc/contentful/rich_text.rb +97 -13
- data/lib/wcc/contentful/rich_text_renderer.rb +300 -0
- data/lib/wcc/contentful/services.rb +37 -1
- data/lib/wcc/contentful/simple_client/response.rb +1 -1
- data/lib/wcc/contentful/sync_engine.rb +2 -1
- data/lib/wcc/contentful/version.rb +1 -1
- data/lib/wcc/contentful.rb +2 -0
- metadata +44 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 81257133691833969ac9f77447b91d2698aa0738964b56f80e8533dd403e90d1
|
4
|
+
data.tar.gz: 424597dcc95a76513d70226d82aafbea7baf614dcc7c5ee55c405e2dc75d4337
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23e37d1e5e4271c0f0a13f8e0aa495473b55164b855b58df1638de5fc88f50e00499a4726908f1bb59856075833ab275fa3a893233fc484ff6c839fd42f023b0
|
7
|
+
data.tar.gz: 139b207faa2d1f6a3ae44ddff0421d4572f7b93aab04c2fc63bd6136e05faa72886d639ac023c0009af0934a74d8c70b79d7ce23876903706dd9702c04fb5f8a
|
data/README.md
CHANGED
@@ -15,6 +15,7 @@ Table of Contents:
|
|
15
15
|
3. [Configuration](#configure)
|
16
16
|
4. [Usage](#usage)
|
17
17
|
1. [Model API](#wcccontentfulmodel-api)
|
18
|
+
* [Rich Text Support](#rich-text-support)
|
18
19
|
2. [Store API](#store-api)
|
19
20
|
3. [Direct CDN client](#direct-cdn-api-simpleclient)
|
20
21
|
4. [Accessing the APIs](#accessing-the-apis-within-application-code)
|
@@ -146,7 +147,7 @@ WCC::Contentful.init!
|
|
146
147
|
```
|
147
148
|
|
148
149
|
All configuration options can be found [in the rubydoc under
|
149
|
-
WCC::Contentful::Configuration](https://watermarkchurch.github.io/wcc-contentful/latest/wcc-contentful/WCC/Contentful/Configuration)
|
150
|
+
WCC::Contentful::Configuration](https://watermarkchurch.github.io/wcc-contentful/latest/wcc-contentful/WCC/Contentful/Configuration)
|
150
151
|
|
151
152
|
## Usage
|
152
153
|
|
@@ -198,6 +199,57 @@ preview_redirect_object.href
|
|
198
199
|
|
199
200
|
See the {WCC::Contentful::Model} documentation for more details.
|
200
201
|
|
202
|
+
#### Rich Text support
|
203
|
+
|
204
|
+
As of version 1.5.0, the Model API supports parsing and rendering Rich Text fields.
|
205
|
+
|
206
|
+
Rich Text fields are retrieved from the API and parsed into the WCC::Contentful::RichText::Document object model.
|
207
|
+
```rb
|
208
|
+
Page.find_by(slug: '/some-slug').my_rich_text
|
209
|
+
# => #<struct WCC::Contentful::RichText::Document ...
|
210
|
+
```
|
211
|
+
|
212
|
+
If you are using Rails, a rich text field can be rendered to HTML using the default renderer by
|
213
|
+
calling #to_html:
|
214
|
+
```rb
|
215
|
+
my_rich_text.to_html
|
216
|
+
# => "<div class=\"contentful-rich-text\"><h2>Dear Watermark Family,</h2>
|
217
|
+
```
|
218
|
+
|
219
|
+
If you are not using Rails, or if you want to override the default rendering behavior, you need to set the
|
220
|
+
WCC::Contentful::Configuration#rich_text_field configuration option:
|
221
|
+
```rb
|
222
|
+
# lib/my_rich_text_renderer
|
223
|
+
class MyRichTextRenderer < WCC::Contentful::ActionViewRichTextRenderer
|
224
|
+
def render_hyperlink(node)
|
225
|
+
# override the default logic for rendering hyperlinks
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# config/initializers/wcc_contentful.rb
|
230
|
+
WCC::Contentful.configure do |config|
|
231
|
+
config.rich_text_renderer = MyRichTextRenderer
|
232
|
+
end
|
233
|
+
```
|
234
|
+
|
235
|
+
If you want to construct and render WCC::Contentful::RichText::Document objects directly, the #to_html method will
|
236
|
+
raise an error. Instead, you will need to construct and invoke your renderer directly.
|
237
|
+
```rb
|
238
|
+
my_document = WCC::Contentful::RichText.tokenize(JSON.parse(...contentful CDN rich text field representation...))
|
239
|
+
# => #<struct WCC::Contentful::RichText::Document ...
|
240
|
+
|
241
|
+
renderer = MyRichTextRenderer.new(my_document,
|
242
|
+
# (optional) inject services so the renderer can automatically resolve links to entries and assets.
|
243
|
+
# The renderer still works without this, but hyperlinks which reference Assets or Entries will raise an error.
|
244
|
+
config: WCC::Contentful.configuration,
|
245
|
+
store: WCC::Contentful::Services.instance.store,
|
246
|
+
model_namespace: WCC::Contentful::Model)
|
247
|
+
# => #<MyRichTextRenderer:0x0000000005c71a78
|
248
|
+
|
249
|
+
renderer.call
|
250
|
+
# => "<div class=\"contentful-rich-text\"><h2>Dear Watermark Family,</h2>
|
251
|
+
```
|
252
|
+
|
201
253
|
### Store API
|
202
254
|
|
203
255
|
The Store layer is used by the Model API to access Contentful data in a raw form.
|
@@ -359,7 +411,7 @@ From the bottom up:
|
|
359
411
|
|
360
412
|
### Client Layer
|
361
413
|
|
362
|
-
The {WCC::Contentful::SimpleClient} provides methods to access the [Contentful
|
414
|
+
The {WCC::Contentful::SimpleClient} provides methods to access the [Contentful
|
363
415
|
Content Delivery API](https://www.contentful.com/developers/docs/references/content-delivery-api/)
|
364
416
|
through your favorite HTTP client gem. The SimpleClient expects
|
365
417
|
an Adapter that conforms to the Faraday interface.
|
@@ -403,7 +455,7 @@ require 'wcc/contentful/store/rspec_examples'
|
|
403
455
|
|
404
456
|
RSpec.describe MyStore do
|
405
457
|
it_behaves_like 'contentful store', {
|
406
|
-
# Set which store features your store implements.
|
458
|
+
# Set which store features your store implements.
|
407
459
|
nested_queries: true, # Does your store implement JOINs?
|
408
460
|
include_param: true # Does your store resolve links when given the :include option?
|
409
461
|
}
|
@@ -695,7 +747,7 @@ defined inside the `app` directory, this will have the effect of deleting all co
|
|
695
747
|
as well as the constants generated from your schema.
|
696
748
|
This will result in one of two errors:
|
697
749
|
|
698
|
-
* `NameError (uninitialized constant MySecondSpace::MyContentType)`
|
750
|
+
* `NameError (uninitialized constant MySecondSpace::MyContentType)`
|
699
751
|
if you try to reference a subclass such as `MyContentType < MySecondSpace::MyContentType`
|
700
752
|
* `ArgumentError (Not yet configured!)`
|
701
753
|
if you try to `MySecondSpace.find('xxxx')` to load an Entry or Asset
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'wcc/contentful'
|
4
|
+
|
5
|
+
namespace :wcc_contentful do
|
6
|
+
desc 'Rewrites JSON fixtures from locale=* to locale=en-US'
|
7
|
+
task :rewrite_fixtures, %i[glob locale] => :environment do |_t, args|
|
8
|
+
glob = args[:glob] || 'spec/fixtures/**/*.json'
|
9
|
+
locale = args[:locale] || 'en-US'
|
10
|
+
|
11
|
+
Dir.glob(glob) do |filename|
|
12
|
+
next unless File.file?(filename)
|
13
|
+
|
14
|
+
contents = JSON.parse(File.read(filename))
|
15
|
+
next unless contents.is_a?(Hash) && contents['sys']
|
16
|
+
|
17
|
+
rewritten_contents =
|
18
|
+
case contents['sys']['type']
|
19
|
+
when 'Array'
|
20
|
+
rewrite_array(contents, locale: locale)
|
21
|
+
when 'Entry', 'Asset'
|
22
|
+
rewrite_entry(contents, locale: locale)
|
23
|
+
end
|
24
|
+
next unless rewritten_contents
|
25
|
+
|
26
|
+
File.write(filename, JSON.pretty_generate(rewritten_contents))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def rewrite_array(contents, locale: 'en-US')
|
31
|
+
contents['items'] =
|
32
|
+
contents['items'].map do |item|
|
33
|
+
rewrite_entry(item, locale: locale)
|
34
|
+
end
|
35
|
+
if contents['includes']
|
36
|
+
if contents['includes']['Entry']
|
37
|
+
contents['includes']['Entry'] =
|
38
|
+
contents['includes']['Entry'].map do |item|
|
39
|
+
rewrite_entry(item, locale: locale)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
if contents['includes']['Asset']
|
43
|
+
contents['includes']['Asset'] =
|
44
|
+
contents['includes']['Asset'].map do |item|
|
45
|
+
rewrite_entry(item, locale: locale)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
contents
|
51
|
+
end
|
52
|
+
|
53
|
+
def rewrite_entry(contents, locale: 'en-US')
|
54
|
+
return contents unless contents['sys']
|
55
|
+
|
56
|
+
WCC::Contentful::EntryLocaleTransformer.transform_to_locale(
|
57
|
+
contents,
|
58
|
+
locale
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'action_view'
|
4
|
+
|
5
|
+
# An implementation of the RichTextRenderer that uses ActionView helpers to implement content_tag and concat.
|
6
|
+
class WCC::Contentful::ActionViewRichTextRenderer < WCC::Contentful::RichTextRenderer
|
7
|
+
include ActionView::Helpers::TagHelper
|
8
|
+
include ActionView::Helpers::TextHelper
|
9
|
+
include ActionView::Context
|
10
|
+
|
11
|
+
# TODO: use ActionView view context to render ERB templates for embedded entries?
|
12
|
+
end
|
@@ -14,6 +14,7 @@ class WCC::Contentful::Configuration
|
|
14
14
|
logger
|
15
15
|
management_token
|
16
16
|
preview_token
|
17
|
+
rich_text_renderer
|
17
18
|
schema_file
|
18
19
|
space
|
19
20
|
store
|
@@ -77,6 +78,12 @@ class WCC::Contentful::Configuration
|
|
77
78
|
# Default: 2.seconds
|
78
79
|
attr_accessor :sync_retry_wait
|
79
80
|
|
81
|
+
# Sets the rich text renderer implementation. This must be a class that accepts a WCC::Contentful::RichText::Document
|
82
|
+
# in the constructor, and responds to `:call` with a string containing the HTML.
|
83
|
+
# In a Rails context, the implementation defaults to WCC::Contentful::ActionViewRichTextRenderer.
|
84
|
+
# In a non-Rails context, you must provide your own implementation.
|
85
|
+
attr_accessor :rich_text_renderer
|
86
|
+
|
80
87
|
# Returns true if the currently configured environment is pointing at `master`.
|
81
88
|
def master?
|
82
89
|
!environment.present?
|
@@ -204,6 +211,12 @@ class WCC::Contentful::Configuration
|
|
204
211
|
}
|
205
212
|
@management_token = ENV.fetch('CONTENTFUL_MANAGEMENT_TOKEN', nil)
|
206
213
|
@preview_token = ENV.fetch('CONTENTFUL_PREVIEW_TOKEN', nil)
|
214
|
+
|
215
|
+
if defined?(ActionView)
|
216
|
+
require 'wcc/contentful/action_view_rich_text_renderer'
|
217
|
+
@rich_text_renderer = WCC::Contentful::ActionViewRichTextRenderer
|
218
|
+
end
|
219
|
+
|
207
220
|
@space = ENV.fetch('CONTENTFUL_SPACE_ID', nil)
|
208
221
|
@default_locale = 'en-US'
|
209
222
|
@locale_fallbacks = {}
|
@@ -22,20 +22,21 @@ module WCC::Contentful::EntryLocaleTransformer
|
|
22
22
|
return entry
|
23
23
|
end
|
24
24
|
|
25
|
-
|
25
|
+
new_entry = entry.dup
|
26
|
+
|
27
|
+
new_entry['sys'] = entry['sys'].except('locale').merge({
|
26
28
|
'WCC::Contentful::EntryLocaleTransformer:locales_included' => [entry_locale]
|
27
29
|
})
|
28
|
-
fields
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
if entry.key?('fields')
|
31
|
+
new_entry['fields'] =
|
32
|
+
entry['fields'].transform_values do |value|
|
33
|
+
h = {}
|
34
|
+
h[entry_locale] = value
|
35
|
+
h
|
36
|
+
end
|
37
|
+
end
|
34
38
|
|
35
|
-
|
36
|
-
'sys' => sys,
|
37
|
-
'fields' => fields
|
38
|
-
}
|
39
|
+
new_entry
|
39
40
|
end
|
40
41
|
|
41
42
|
##
|
@@ -54,29 +55,29 @@ module WCC::Contentful::EntryLocaleTransformer
|
|
54
55
|
# Transform the store's "locale=*" entry into a localized one
|
55
56
|
locale ||= default_locale
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
v =
|
68
|
-
|
69
|
-
|
70
|
-
|
58
|
+
new_entry = entry.dup
|
59
|
+
new_entry['sys'] = entry['sys'].deep_dup
|
60
|
+
new_entry['sys']['locale'] = locale
|
61
|
+
if entry.key?('fields')
|
62
|
+
new_entry['fields'] =
|
63
|
+
entry['fields']&.transform_values do |value|
|
64
|
+
next if value.nil?
|
65
|
+
|
66
|
+
# replace the all-locales value with the localized value
|
67
|
+
l = locale
|
68
|
+
v = nil
|
69
|
+
while l
|
70
|
+
v = value[l]
|
71
|
+
break if v
|
72
|
+
|
73
|
+
l = configuration.locale_fallbacks[l]
|
74
|
+
end
|
75
|
+
|
76
|
+
v
|
71
77
|
end
|
78
|
+
end
|
72
79
|
|
73
|
-
|
74
|
-
end
|
75
|
-
|
76
|
-
{
|
77
|
-
'sys' => sys,
|
78
|
-
'fields' => fields
|
79
|
-
}
|
80
|
+
new_entry
|
80
81
|
end
|
81
82
|
|
82
83
|
##
|
@@ -22,9 +22,11 @@ module WCC::Contentful::Middleware::Store
|
|
22
22
|
found =
|
23
23
|
@cache.fetch(key, expires_in: expires_in) do
|
24
24
|
event = 'miss'
|
25
|
-
# if it's
|
25
|
+
# if it's from the sync engine don't hit the API.
|
26
|
+
next if key =~ /^sync:/
|
27
|
+
|
26
28
|
# Store a nil object if we can't find the object on the CDN.
|
27
|
-
(store.find(key, **options) || nil_obj(key))
|
29
|
+
(store.find(key, **options) || nil_obj(key))
|
28
30
|
end
|
29
31
|
|
30
32
|
return unless found
|
@@ -37,7 +37,7 @@ module WCC::Contentful::ModelAPI
|
|
37
37
|
# try looking up the class heierarchy
|
38
38
|
(superclass.services if superclass.respond_to?(:services)) ||
|
39
39
|
# create it if we have a configuration
|
40
|
-
WCC::Contentful::Services.new(configuration)
|
40
|
+
WCC::Contentful::Services.new(configuration, model_namespace: self)
|
41
41
|
end
|
42
42
|
|
43
43
|
def store(preview = nil)
|
@@ -104,7 +104,7 @@ module WCC::Contentful
|
|
104
104
|
# when :DateTime
|
105
105
|
# raw_value = Time.parse(raw_value).localtime
|
106
106
|
when :RichText
|
107
|
-
raw_value = WCC::Contentful::RichText.tokenize(raw_value)
|
107
|
+
raw_value = WCC::Contentful::RichText.tokenize(raw_value, renderer: ns.services.rich_text_renderer)
|
108
108
|
when :Int
|
109
109
|
raw_value = Integer(raw_value)
|
110
110
|
when :Float
|
@@ -28,6 +28,24 @@ module WCC::Contentful::RichText
|
|
28
28
|
tuple
|
29
29
|
end
|
30
30
|
end
|
31
|
+
|
32
|
+
# Set the renderer to use when rendering this node to HTML.
|
33
|
+
# By default a node does not have a renderer, and #to_html will raise an ArgumentError.
|
34
|
+
# However if a WCC::Contentful::RichText::Document node is created by the WCC::Contentful::ModelBuilder,
|
35
|
+
# it will inject the renderer configured in WCC::Contentful::Services#rich_text_renderer into the node.
|
36
|
+
attr_accessor :renderer
|
37
|
+
|
38
|
+
# Render the node to HTML using the configured renderer.
|
39
|
+
# See WCC::Contentful::RichTextRenderer for more information.
|
40
|
+
def to_html
|
41
|
+
unless renderer
|
42
|
+
raise ArgumentError,
|
43
|
+
'No renderer provided during tokenization. ' \
|
44
|
+
'Please configure the rich_text_renderer in your WCC::Contentful configuration.'
|
45
|
+
end
|
46
|
+
|
47
|
+
renderer.call(self)
|
48
|
+
end
|
31
49
|
end
|
32
50
|
|
33
51
|
class_methods do
|
@@ -36,8 +54,12 @@ module WCC::Contentful::RichText
|
|
36
54
|
name.demodulize.underscore.dasherize
|
37
55
|
end
|
38
56
|
|
39
|
-
def
|
40
|
-
|
57
|
+
def matches?(node_type)
|
58
|
+
self.node_type == node_type
|
59
|
+
end
|
60
|
+
|
61
|
+
def tokenize(raw, renderer: nil)
|
62
|
+
raise ArgumentError, "Expected '#{node_type}', got '#{raw['nodeType']}'" unless matches?(raw['nodeType'])
|
41
63
|
|
42
64
|
values =
|
43
65
|
members.map do |symbol|
|
@@ -45,15 +67,17 @@ module WCC::Contentful::RichText
|
|
45
67
|
|
46
68
|
case symbol
|
47
69
|
when :content
|
48
|
-
WCC::Contentful::RichText.tokenize(val,
|
49
|
-
# when :data
|
50
|
-
# TODO: resolve links...
|
70
|
+
WCC::Contentful::RichText.tokenize(val, renderer: renderer)
|
51
71
|
else
|
52
72
|
val
|
53
73
|
end
|
54
74
|
end
|
55
75
|
|
56
|
-
new(*values)
|
76
|
+
new(*values).tap do |node|
|
77
|
+
next unless renderer
|
78
|
+
|
79
|
+
node.renderer = renderer
|
80
|
+
end
|
57
81
|
end
|
58
82
|
end
|
59
83
|
end
|
@@ -23,9 +23,11 @@ require_relative './rich_text/node'
|
|
23
23
|
module WCC::Contentful::RichText
|
24
24
|
##
|
25
25
|
# Recursively converts a raw JSON-parsed hash into the RichText object model.
|
26
|
-
|
26
|
+
# If renderer are provided, the model will be able to resolve links to entries
|
27
|
+
# and enable direct rendering of documents to HTML.
|
28
|
+
def self.tokenize(raw, renderer: nil)
|
27
29
|
return unless raw
|
28
|
-
return raw.map { |c| tokenize(c
|
30
|
+
return raw.map { |c| tokenize(c) } if raw.is_a?(Array)
|
29
31
|
|
30
32
|
klass =
|
31
33
|
case raw['nodeType']
|
@@ -33,10 +35,26 @@ module WCC::Contentful::RichText
|
|
33
35
|
Document
|
34
36
|
when 'paragraph'
|
35
37
|
Paragraph
|
38
|
+
when 'hr'
|
39
|
+
HR
|
36
40
|
when 'blockquote'
|
37
41
|
Blockquote
|
38
42
|
when 'text'
|
39
43
|
Text
|
44
|
+
when 'ordered-list'
|
45
|
+
OrderedList
|
46
|
+
when 'unordered-list'
|
47
|
+
UnorderedList
|
48
|
+
when 'list-item'
|
49
|
+
ListItem
|
50
|
+
when 'table'
|
51
|
+
Table
|
52
|
+
when 'table-row'
|
53
|
+
TableRow
|
54
|
+
when 'table-cell'
|
55
|
+
TableCell
|
56
|
+
when 'table-header-cell'
|
57
|
+
TableHeaderCell
|
40
58
|
when 'embedded-entry-inline'
|
41
59
|
EmbeddedEntryInline
|
42
60
|
when 'embedded-entry-block'
|
@@ -44,13 +62,17 @@ module WCC::Contentful::RichText
|
|
44
62
|
when 'embedded-asset-block'
|
45
63
|
EmbeddedAssetBlock
|
46
64
|
when /heading-(\d+)/
|
47
|
-
|
48
|
-
|
65
|
+
Heading
|
66
|
+
when /(\w+-)?hyperlink/
|
67
|
+
Hyperlink
|
49
68
|
else
|
69
|
+
# Future proofing for new node types introduced by Contentful.
|
70
|
+
# The best list of node types maintained by Contentful is here:
|
71
|
+
# https://github.com/contentful/rich-text/blob/master/packages/rich-text-types/src/blocks.ts
|
50
72
|
Unknown
|
51
73
|
end
|
52
74
|
|
53
|
-
klass.tokenize(raw,
|
75
|
+
klass.tokenize(raw, renderer: renderer)
|
54
76
|
end
|
55
77
|
|
56
78
|
Document =
|
@@ -63,6 +85,11 @@ module WCC::Contentful::RichText
|
|
63
85
|
include WCC::Contentful::RichText::Node
|
64
86
|
end
|
65
87
|
|
88
|
+
HR =
|
89
|
+
Struct.new(:nodeType, :data, :content) do
|
90
|
+
include WCC::Contentful::RichText::Node
|
91
|
+
end
|
92
|
+
|
66
93
|
Blockquote =
|
67
94
|
Struct.new(:nodeType, :data, :content) do
|
68
95
|
include WCC::Contentful::RichText::Node
|
@@ -73,6 +100,41 @@ module WCC::Contentful::RichText
|
|
73
100
|
include WCC::Contentful::RichText::Node
|
74
101
|
end
|
75
102
|
|
103
|
+
OrderedList =
|
104
|
+
Struct.new(:nodeType, :data, :content) do
|
105
|
+
include WCC::Contentful::RichText::Node
|
106
|
+
end
|
107
|
+
|
108
|
+
UnorderedList =
|
109
|
+
Struct.new(:nodeType, :data, :content) do
|
110
|
+
include WCC::Contentful::RichText::Node
|
111
|
+
end
|
112
|
+
|
113
|
+
ListItem =
|
114
|
+
Struct.new(:nodeType, :data, :content) do
|
115
|
+
include WCC::Contentful::RichText::Node
|
116
|
+
end
|
117
|
+
|
118
|
+
Table =
|
119
|
+
Struct.new(:nodeType, :data, :content) do
|
120
|
+
include WCC::Contentful::RichText::Node
|
121
|
+
end
|
122
|
+
|
123
|
+
TableRow =
|
124
|
+
Struct.new(:nodeType, :data, :content) do
|
125
|
+
include WCC::Contentful::RichText::Node
|
126
|
+
end
|
127
|
+
|
128
|
+
TableCell =
|
129
|
+
Struct.new(:nodeType, :data, :content) do
|
130
|
+
include WCC::Contentful::RichText::Node
|
131
|
+
end
|
132
|
+
|
133
|
+
TableHeaderCell =
|
134
|
+
Struct.new(:nodeType, :data, :content) do
|
135
|
+
include WCC::Contentful::RichText::Node
|
136
|
+
end
|
137
|
+
|
76
138
|
EmbeddedEntryInline =
|
77
139
|
Struct.new(:nodeType, :data, :content) do
|
78
140
|
include WCC::Contentful::RichText::Node
|
@@ -88,18 +150,40 @@ module WCC::Contentful::RichText
|
|
88
150
|
include WCC::Contentful::RichText::Node
|
89
151
|
end
|
90
152
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
153
|
+
EmbeddedResourceBlock =
|
154
|
+
Struct.new(:nodeType, :data, :content) do
|
155
|
+
include WCC::Contentful::RichText::Node
|
156
|
+
end
|
157
|
+
|
158
|
+
Heading =
|
159
|
+
Struct.new(:nodeType, :data, :content) do
|
160
|
+
include WCC::Contentful::RichText::Node
|
161
|
+
|
162
|
+
def self.matches?(node_type)
|
163
|
+
node_type =~ /heading-(\d+)/
|
95
164
|
end
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
165
|
+
|
166
|
+
def size
|
167
|
+
@size ||= /heading-(\d+)/.match(nodeType)[1]&.to_i
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
Hyperlink =
|
172
|
+
Struct.new(:nodeType, :data, :content) do
|
173
|
+
include WCC::Contentful::RichText::Node
|
174
|
+
|
175
|
+
def self.matches?(node_type)
|
176
|
+
node_type =~ /(\w+-)?hyperlink/
|
177
|
+
end
|
178
|
+
end
|
100
179
|
|
101
180
|
Unknown =
|
102
181
|
Struct.new(:nodeType, :data, :content) do
|
103
182
|
include WCC::Contentful::RichText::Node
|
183
|
+
|
184
|
+
# Unknown nodes are the catch all, so they always match anything that made it to the else case of the switch.
|
185
|
+
def self.matches?(_node_type)
|
186
|
+
true
|
187
|
+
end
|
104
188
|
end
|
105
189
|
end
|
@@ -0,0 +1,300 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The abstract base class for rendering Rich Text.
|
4
|
+
# This base class implements much of the recursive logic necessary for rendering
|
5
|
+
# Rich Text nodes, but leaves the actual rendering of the HTML tags to the
|
6
|
+
# subclasses.
|
7
|
+
#
|
8
|
+
# Subclasses can override any method to customize the rendering behavior. At a minimum they must implement
|
9
|
+
# the #content_tag and #concat methods to take advantage of the recursive rendering logic in the base class.
|
10
|
+
# The API for these methods is assumed to be equivalent to the ActionView helpers of the same name.
|
11
|
+
#
|
12
|
+
# The canonical implementation is the WCC::Contentful::ActionViewRichTextRenderer, which uses the standard ActionView
|
13
|
+
# helpers as-is to render the HTML tags.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# class MyRichTextRenderer < WCC::Contentful::RichTextRenderer
|
17
|
+
# def content_tag(name, options, &block)
|
18
|
+
# # your implementation here
|
19
|
+
# # for reference of expected behavior see
|
20
|
+
# # https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-content_tag
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# def concat(html_string)
|
24
|
+
# # your implementation here
|
25
|
+
# # for reference of expected behavior see
|
26
|
+
# # https://api.rubyonrails.org/classes/ActionView/Helpers/TextHelper.html#method-i-concat
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# renderer = MyRichTextRenderer.new(document)
|
31
|
+
# renderer.call
|
32
|
+
#
|
33
|
+
# @abstract
|
34
|
+
class WCC::Contentful::RichTextRenderer
|
35
|
+
class << self
|
36
|
+
def call(document, *args, **kwargs)
|
37
|
+
new(document, *args, **kwargs).call
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :document
|
42
|
+
attr_accessor :config, :store, :model_namespace
|
43
|
+
|
44
|
+
def initialize(document, config: nil, store: nil, model_namespace: nil)
|
45
|
+
@document = document
|
46
|
+
@config = config if config.present?
|
47
|
+
@store = store if store.present?
|
48
|
+
@model_namespace = model_namespace if model_namespace.present?
|
49
|
+
end
|
50
|
+
|
51
|
+
def call
|
52
|
+
render.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
def render
|
56
|
+
content_tag(:div, class: 'contentful-rich-text') do
|
57
|
+
render_content(document.content)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def render_content(content)
|
62
|
+
content&.each do |node|
|
63
|
+
concat render_node(node)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def render_node(node)
|
68
|
+
if WCC::Contentful::RichText::Heading.matches?(node.node_type)
|
69
|
+
render_heading(node)
|
70
|
+
else
|
71
|
+
public_send(:"render_#{node.node_type.underscore}", node)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def render_text(node)
|
76
|
+
return node.value unless node.marks&.any?
|
77
|
+
|
78
|
+
node.marks.reduce(node.value) do |value, mark|
|
79
|
+
next value unless type = mark['type']&.underscore
|
80
|
+
|
81
|
+
render_mark(type, value)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
DEFAULT_MARKS = {
|
86
|
+
'bold' => 'strong',
|
87
|
+
'italic' => 'em',
|
88
|
+
'underline' => 'u',
|
89
|
+
'code' => 'code',
|
90
|
+
'superscript' => 'sup',
|
91
|
+
'subscript' => 'sub'
|
92
|
+
}.freeze
|
93
|
+
|
94
|
+
def render_mark(type, value)
|
95
|
+
return value unless tag = DEFAULT_MARKS[type]
|
96
|
+
|
97
|
+
content_tag(tag, value)
|
98
|
+
end
|
99
|
+
|
100
|
+
def render_paragraph(node)
|
101
|
+
content_tag(:p) do
|
102
|
+
render_content(node.content)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def render_heading(node)
|
107
|
+
content_tag(:"h#{node.size}") do
|
108
|
+
render_content(node.content)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def render_blockquote(node)
|
113
|
+
content_tag(:blockquote) do
|
114
|
+
render_content(node.content)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def render_hr(_node)
|
119
|
+
content_tag(:hr)
|
120
|
+
end
|
121
|
+
|
122
|
+
def render_unordered_list(node)
|
123
|
+
content_tag(:ul) do
|
124
|
+
render_content(node.content)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def render_ordered_list(node)
|
129
|
+
content_tag(:ol) do
|
130
|
+
render_content(node.content)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def render_list_item(node)
|
135
|
+
content_tag(:li) do
|
136
|
+
render_content(node.content)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def render_table(node)
|
141
|
+
content_tag(:table) do
|
142
|
+
# Check the first row - if it's a header row, render a <thead>
|
143
|
+
first, *rest = node.content
|
144
|
+
if first&.content&.all? { |cell| cell.node_type == 'table-header-cell' }
|
145
|
+
concat(content_tag(:thead) { render_content([first]) })
|
146
|
+
else
|
147
|
+
# Otherwise, render it inside the tbody with the rest
|
148
|
+
rest.unshift(first)
|
149
|
+
end
|
150
|
+
|
151
|
+
concat(content_tag(:tbody) { render_content(rest) })
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def render_table_row(node)
|
156
|
+
content_tag(:tr) do
|
157
|
+
render_content(node.content)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def render_table_cell(node)
|
162
|
+
content_tag(:td) do
|
163
|
+
render_content(node.content)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def render_table_header_cell(node)
|
168
|
+
content_tag(:th) do
|
169
|
+
render_content(node.content)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def render_hyperlink(node)
|
174
|
+
content_tag(:a,
|
175
|
+
href: node.data['uri'],
|
176
|
+
# External links should be target="_blank" by default
|
177
|
+
target: ('_blank' if url_is_external?(node.data['uri']))) do
|
178
|
+
render_content(node.content)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def render_asset_hyperlink(node)
|
183
|
+
target = resolve_target(node.data['target'])
|
184
|
+
url = target&.dig('fields', 'file', 'url')
|
185
|
+
|
186
|
+
render_hyperlink(
|
187
|
+
WCC::Contentful::RichText::Hyperlink.tokenize(
|
188
|
+
node.as_json.merge(
|
189
|
+
'nodeType' => 'hyperlink',
|
190
|
+
'data' => node['data'].merge({
|
191
|
+
'uri' => url,
|
192
|
+
'target' => target.as_json
|
193
|
+
})
|
194
|
+
)
|
195
|
+
)
|
196
|
+
)
|
197
|
+
end
|
198
|
+
|
199
|
+
def render_entry_hyperlink(node)
|
200
|
+
unless model_namespace.present?
|
201
|
+
raise NotConnectedError,
|
202
|
+
'Rendering linked entries requires a connected RichTextRenderer. Please use the one configured in ' \
|
203
|
+
'WCC::Contentful::Services.instance or pass a model_namespace to the RichTextRenderer constructor.'
|
204
|
+
end
|
205
|
+
|
206
|
+
target = resolve_target(node.data['target'])
|
207
|
+
model_instance = model_namespace.new_from_raw(target)
|
208
|
+
unless model_instance.respond_to?(:href)
|
209
|
+
raise NotConnectedError,
|
210
|
+
"Entry hyperlinks are not supported for #{model_instance.class}. " \
|
211
|
+
'Please ensure your model defines an #href method, or override the ' \
|
212
|
+
'#render_entry_hyperlink method in your app-specific RichTextRenderer implementation.'
|
213
|
+
end
|
214
|
+
|
215
|
+
render_hyperlink(
|
216
|
+
WCC::Contentful::RichText::Hyperlink.tokenize(
|
217
|
+
node.as_json.merge(
|
218
|
+
'nodeType' => 'hyperlink',
|
219
|
+
'data' => node['data'].merge({
|
220
|
+
'uri' => model_instance.href,
|
221
|
+
'target' => target.as_json
|
222
|
+
})
|
223
|
+
)
|
224
|
+
)
|
225
|
+
)
|
226
|
+
end
|
227
|
+
|
228
|
+
def render_embedded_asset_block(node)
|
229
|
+
target = resolve_target(node.data['target'])
|
230
|
+
title = target&.dig('fields', 'title')
|
231
|
+
url = target&.dig('fields', 'file', 'url')
|
232
|
+
|
233
|
+
content_tag(:img, src: url, alt: title) do
|
234
|
+
render_content(node.content)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def render_embedded_entry_block(_node)
|
239
|
+
raise AbstractRendererError,
|
240
|
+
'Entry embeds are not supported. What should it look like? ' \
|
241
|
+
'Please override this in your app-specific RichTextRenderer implementation.'
|
242
|
+
end
|
243
|
+
|
244
|
+
def render_embedded_entry_inline(_node)
|
245
|
+
raise AbstractRendererError,
|
246
|
+
'Inline Entry embeds are not supported. What should it look like? ' \
|
247
|
+
'Please override this in your app-specific RichTextRenderer implementation.'
|
248
|
+
end
|
249
|
+
|
250
|
+
private
|
251
|
+
|
252
|
+
def resolve_target(target)
|
253
|
+
unless store.present?
|
254
|
+
raise NotConnectedError,
|
255
|
+
'Rendering embedded or linked entries requires a connected RichTextRenderer. Please use the one configured ' \
|
256
|
+
'in WCC::Contentful::Services.instance or pass a store to the RichTextRenderer constructor.'
|
257
|
+
end
|
258
|
+
|
259
|
+
if target&.dig('sys', 'type') == 'Link'
|
260
|
+
target = store.find(target.dig('sys', 'id'), hint: target.dig('sys', 'linkType'))
|
261
|
+
end
|
262
|
+
target
|
263
|
+
end
|
264
|
+
|
265
|
+
def url_is_external?(url)
|
266
|
+
return false unless url.present?
|
267
|
+
|
268
|
+
uri =
|
269
|
+
begin
|
270
|
+
URI(url)
|
271
|
+
rescue StandardError
|
272
|
+
nil
|
273
|
+
end
|
274
|
+
return false unless uri&.host.present?
|
275
|
+
|
276
|
+
app_uri =
|
277
|
+
if config&.app_url.present?
|
278
|
+
begin
|
279
|
+
URI(config.app_url)
|
280
|
+
rescue StandardError
|
281
|
+
nil
|
282
|
+
end
|
283
|
+
end
|
284
|
+
uri.host != app_uri&.host
|
285
|
+
end
|
286
|
+
|
287
|
+
def content_tag(*_args)
|
288
|
+
raise AbstractRendererError, 'RichTextRenderer is an abstract class, please use an implementation subclass'
|
289
|
+
end
|
290
|
+
|
291
|
+
def concat(*_args)
|
292
|
+
raise AbstractRendererError, 'RichTextRenderer is an abstract class, please use an implementation subclass'
|
293
|
+
end
|
294
|
+
|
295
|
+
class AbstractRendererError < StandardError
|
296
|
+
end
|
297
|
+
|
298
|
+
class NotConnectedError < AbstractRendererError
|
299
|
+
end
|
300
|
+
end
|
@@ -11,10 +11,11 @@ module WCC::Contentful
|
|
11
11
|
|
12
12
|
attr_reader :configuration
|
13
13
|
|
14
|
-
def initialize(configuration)
|
14
|
+
def initialize(configuration, model_namespace: nil)
|
15
15
|
raise ArgumentError, 'Not yet configured!' unless configuration
|
16
16
|
|
17
17
|
@configuration = configuration
|
18
|
+
@model_namespace = model_namespace
|
18
19
|
end
|
19
20
|
|
20
21
|
# Gets the data-store which executes the queries run against the dynamic
|
@@ -123,6 +124,41 @@ module WCC::Contentful
|
|
123
124
|
end
|
124
125
|
end
|
125
126
|
|
127
|
+
# Returns a callable object which can be used to render a rich text document.
|
128
|
+
# This object will have all the connected services injected into it.
|
129
|
+
# The implementation class is configured by {WCC::Contentful::Configuration#rich_text_renderer}.
|
130
|
+
# In a rails context the default implementation is {WCC::Contentful::ActionViewRichTextRenderer}.
|
131
|
+
def rich_text_renderer
|
132
|
+
@rich_text_renderer ||=
|
133
|
+
if implementation_class = configuration&.rich_text_renderer
|
134
|
+
store = self.store
|
135
|
+
config = configuration
|
136
|
+
model_namespace = @model_namespace || WCC::Contentful::Model
|
137
|
+
|
138
|
+
# Wrap the implementation in a subclass that injects the services
|
139
|
+
Class.new(implementation_class) do
|
140
|
+
define_method :initialize do |document, *args, **kwargs|
|
141
|
+
# Implementation might choose to override these, so call super last
|
142
|
+
@store = store
|
143
|
+
@config = config
|
144
|
+
@model_namespace = model_namespace
|
145
|
+
super(document, *args, **kwargs)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
else
|
149
|
+
# Create a renderer that renders a more helpful error message, but delay the error message until #to_html
|
150
|
+
# is actually invoked in case the user never actually uses the renderer.
|
151
|
+
Class.new(WCC::Contentful::RichTextRenderer) do
|
152
|
+
def call
|
153
|
+
raise WCC::Contentful::RichTextRenderer::AbstractRendererError,
|
154
|
+
'No rich text renderer implementation has been configured. ' \
|
155
|
+
'Please install a supported implementation such as ActionView, ' \
|
156
|
+
'or set WCC::Contentful.configuration.rich_text_renderer to a custom implementation.'
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
126
162
|
# Gets the configured instrumentation adapter, defaulting to ActiveSupport::Notifications
|
127
163
|
def instrumentation
|
128
164
|
@instrumentation ||=
|
@@ -104,7 +104,7 @@ class WCC::Contentful::SimpleClient
|
|
104
104
|
def includes
|
105
105
|
@includes ||=
|
106
106
|
raw['includes']&.each_with_object({}) do |(_t, entries), h|
|
107
|
-
entries
|
107
|
+
entries&.each { |e| h[e.dig('sys', 'id')] = e }
|
108
108
|
end || {}
|
109
109
|
end
|
110
110
|
end
|
@@ -138,7 +138,8 @@ module WCC::Contentful
|
|
138
138
|
# This job uses the Contentful Sync API to update the configured store with
|
139
139
|
# the latest data from Contentful.
|
140
140
|
class Job < ActiveJob::Base
|
141
|
-
|
141
|
+
# This should always be "async", because the configured store could be an in-memory store.
|
142
|
+
self.queue_adapter = :async
|
142
143
|
queue_as :default
|
143
144
|
|
144
145
|
def configuration
|
data/lib/wcc/contentful.rb
CHANGED
@@ -20,6 +20,8 @@ require 'wcc/contentful/model'
|
|
20
20
|
require 'wcc/contentful/model_methods'
|
21
21
|
require 'wcc/contentful/model_singleton_methods'
|
22
22
|
require 'wcc/contentful/model_builder'
|
23
|
+
require 'wcc/contentful/rich_text'
|
24
|
+
require 'wcc/contentful/rich_text_renderer'
|
23
25
|
require 'wcc/contentful/sync_engine'
|
24
26
|
require 'wcc/contentful/events'
|
25
27
|
require 'wcc/contentful/middleware'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wcc-contentful
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Watermark Dev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: byebug
|
@@ -423,7 +423,9 @@ files:
|
|
423
423
|
- config/initializers/mime_types.rb
|
424
424
|
- config/routes.rb
|
425
425
|
- lib/tasks/download_schema.rake
|
426
|
+
- lib/tasks/rewrite_fixtures.rake
|
426
427
|
- lib/wcc/contentful.rb
|
428
|
+
- lib/wcc/contentful/action_view_rich_text_renderer.rb
|
427
429
|
- lib/wcc/contentful/active_record_shim.rb
|
428
430
|
- lib/wcc/contentful/configuration.rb
|
429
431
|
- lib/wcc/contentful/content_type_indexer.rb
|
@@ -451,6 +453,7 @@ files:
|
|
451
453
|
- lib/wcc/contentful/rake.rb
|
452
454
|
- lib/wcc/contentful/rich_text.rb
|
453
455
|
- lib/wcc/contentful/rich_text/node.rb
|
456
|
+
- lib/wcc/contentful/rich_text_renderer.rb
|
454
457
|
- lib/wcc/contentful/rspec.rb
|
455
458
|
- lib/wcc/contentful/services.rb
|
456
459
|
- lib/wcc/contentful/simple_client.rb
|
@@ -495,7 +498,7 @@ homepage: https://github.com/watermarkchurch/wcc-contentful/wcc-contentful
|
|
495
498
|
licenses:
|
496
499
|
- MIT
|
497
500
|
metadata:
|
498
|
-
documentation_uri: https://watermarkchurch.github.io/wcc-contentful/1.
|
501
|
+
documentation_uri: https://watermarkchurch.github.io/wcc-contentful/1.5/wcc-contentful
|
499
502
|
rubygems_mfa_required: 'true'
|
500
503
|
post_install_message:
|
501
504
|
rdoc_options: []
|
@@ -523,11 +526,12 @@ summary: '[](https://
|
|
523
526
|
[contentful_model](https://github.com/contentful/contentful_model), and [contentful_rails](https://github.com/contentful/contentful_rails)
|
524
527
|
gems all in one. Table of Contents: 1. [Why?](#why-did-you-rewrite-the-contentful-ruby-stack)
|
525
528
|
2. [Installation](#installation) 3. [Configuration](#configure) 4. [Usage](#usage)
|
526
|
-
1. [Model API](#wcccontentfulmodel-api)
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
529
|
+
1. [Model API](#wcccontentfulmodel-api) * [Rich Text Support](#rich-text-support)
|
530
|
+
2. [Store API](#store-api) 3. [Direct CDN client](#direct-cdn-api-simpleclient)
|
531
|
+
4. [Accessing the APIs](#accessing-the-apis-within-application-code) 5. [Architecture](#architecture)
|
532
|
+
1. [Client Layer](#client-layer) 2. [Store Layer](#store-layer) 3. [Model Layer](#model-layer)
|
533
|
+
6. [Test Helpers](#test-helpers) 7. [Advanced Configuration Example](#advanced-configuration-example)
|
534
|
+
8. [Connecting to Multiple Spaces](#connecting-to-multiple-spaces-or-environments)
|
531
535
|
9. [Development](#development) 10. [Contributing](#contributing) 11. [License](#license) ##
|
532
536
|
Why did you rewrite the Contentful ruby stack? We started working with Contentful
|
533
537
|
almost 5 years ago. Since that time, Contentful''s ruby stack has improved, but
|
@@ -619,7 +623,7 @@ summary: '[](https://
|
|
619
623
|
$ gem install wcc-contentful ``` ## Configure Put this in an initializer: ```ruby
|
620
624
|
# config/initializers/wcc_contentful.rb WCC::Contentful.configure do |config| config.access_token
|
621
625
|
= <CONTENTFUL_ACCESS_TOKEN> config.space = <CONTENTFUL_SPACE_ID> end WCC::Contentful.init!
|
622
|
-
``` All configuration options can be found [in the rubydoc under WCC::Contentful::Configuration](https://watermarkchurch.github.io/wcc-contentful/latest/wcc-contentful/WCC/Contentful/Configuration)
|
626
|
+
``` All configuration options can be found [in the rubydoc under WCC::Contentful::Configuration](https://watermarkchurch.github.io/wcc-contentful/latest/wcc-contentful/WCC/Contentful/Configuration) ##
|
623
627
|
Usage ### WCC::Contentful::Model API The WCC::Contentful::Model API exposes Contentful
|
624
628
|
data as a set of dynamically generated Ruby objects. These objects are based on
|
625
629
|
the content types in your Contentful space. All these objects are generated by
|
@@ -640,12 +644,34 @@ summary: '[](https://
|
|
640
644
|
= WCC::Contentful::Model::Redirect.find_by({ slug: ''draft-redirect'' }, preview:
|
641
645
|
true) # => #<WCC::Contentful::Model::Redirect:0x0000000005d879ad @created_at=2018-04-16
|
642
646
|
18:41:17 UTC...> preview_redirect_object.href # => ''http://www.somesite.com/slug-for-redirect''
|
643
|
-
``` See the {WCC::Contentful::Model} documentation for more details.
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
647
|
+
``` See the {WCC::Contentful::Model} documentation for more details. #### Rich
|
648
|
+
Text support As of version 1.5.0, the Model API supports parsing and rendering
|
649
|
+
Rich Text fields. Rich Text fields are retrieved from the API and parsed into the
|
650
|
+
WCC::Contentful::RichText::Document object model. ```rb Page.find_by(slug: ''/some-slug'').my_rich_text
|
651
|
+
# => #<struct WCC::Contentful::RichText::Document ... ``` If you are using Rails,
|
652
|
+
a rich text field can be rendered to HTML using the default renderer by calling
|
653
|
+
#to_html: ```rb my_rich_text.to_html # => "<div class=\"contentful-rich-text\"><h2>Dear
|
654
|
+
Watermark Family,</h2> ``` If you are not using Rails, or if you want to override
|
655
|
+
the default rendering behavior, you need to set the WCC::Contentful::Configuration#rich_text_field
|
656
|
+
configuration option: ```rb # lib/my_rich_text_renderer class MyRichTextRenderer
|
657
|
+
< WCC::Contentful::ActionViewRichTextRenderer def render_hyperlink(node) # override
|
658
|
+
the default logic for rendering hyperlinks end end # config/initializers/wcc_contentful.rb
|
659
|
+
WCC::Contentful.configure do |config| config.rich_text_renderer = MyRichTextRenderer
|
660
|
+
end ``` If you want to construct and render WCC::Contentful::RichText::Document
|
661
|
+
objects directly, the #to_html method will raise an error. Instead, you will need
|
662
|
+
to construct and invoke your renderer directly. ```rb my_document = WCC::Contentful::RichText.tokenize(JSON.parse(...contentful
|
663
|
+
CDN rich text field representation...)) # => #<struct WCC::Contentful::RichText::Document
|
664
|
+
... renderer = MyRichTextRenderer.new(my_document, # (optional) inject services
|
665
|
+
so the renderer can automatically resolve links to entries and assets. # The renderer
|
666
|
+
still works without this, but hyperlinks which reference Assets or Entries will
|
667
|
+
raise an error. config: WCC::Contentful.configuration, store: WCC::Contentful::Services.instance.store,
|
668
|
+
model_namespace: WCC::Contentful::Model) # => #<MyRichTextRenderer:0x0000000005c71a78 renderer.call
|
669
|
+
# => "<div class=\"contentful-rich-text\"><h2>Dear Watermark Family,</h2> ``` ###
|
670
|
+
Store API The Store layer is used by the Model API to access Contentful data in
|
671
|
+
a raw form. The Store layer returns entries as hashes parsed from JSON, conforming
|
672
|
+
to the object structure returned from the Contentful CDN. The following examples
|
673
|
+
show how to use the Store API to retrieve raw data from the store: ```ruby store
|
674
|
+
= WCC::Contentful::Services.instance.store # => #<WCC::Contentful::Store::CDNAdapter:0x00007fb92a221498 store.find(''5FsqsbMECsM62e04U8sY4Y'')
|
649
675
|
# => {"sys"=> # ... # "fields"=> # ...} store.find_by(content_type: ''page'',
|
650
676
|
filter: { slug: ''/some-slug'' }) # => {"sys"=> # ... # "fields"=> # ...} query
|
651
677
|
= store.find_all(content_type: ''page'').eq(''group'', ''some-group'') # => #<WCC::Contentful::Store::CDNAdapter::Query:0x00007fa3d40b84f0
|
@@ -692,7 +718,7 @@ summary: '[](https://
|
|
692
718
|
< ApplicationJob include WCC::Contentful::ServiceAccessors def perform Page.find(...) store.find(...) client.entries(...)
|
693
719
|
end end ``` ## Architecture  From
|
694
720
|
the bottom up: ### Client Layer The {WCC::Contentful::SimpleClient} provides methods
|
695
|
-
to access the [Contentful
|
721
|
+
to access the [Contentful Content Delivery API](https://www.contentful.com/developers/docs/references/content-delivery-api/)
|
696
722
|
through your favorite HTTP client gem. The SimpleClient expects an Adapter that
|
697
723
|
conforms to the Faraday interface. Creating a SimpleClient to connect using different
|
698
724
|
credentials, or to connect without setting up all the rest of WCC::Contentful, is
|
@@ -711,7 +737,7 @@ summary: '[](https://
|
|
711
737
|
your own store. In your RSpec suite: ```ruby # frozen_string_literal: true require
|
712
738
|
''my_store'' require ''wcc/contentful/store/rspec_examples'' RSpec.describe MyStore
|
713
739
|
do it_behaves_like ''contentful store'', { # Set which store features your store
|
714
|
-
implements.
|
740
|
+
implements. nested_queries: true, # Does your store implement JOINs? include_param:
|
715
741
|
true # Does your store resolve links when given the :include option? } ``` The
|
716
742
|
store is kept up-to-date by the {WCC::Contentful::SyncEngine}. The `SyncEngine#next`
|
717
743
|
methodcalls the `#index` method on the configured store in order to update it with
|
@@ -827,7 +853,7 @@ summary: '[](https://
|
|
827
853
|
in a class defined inside the `app` directory, this will have the effect of deleting
|
828
854
|
all configuration that was set in the initializer as well as the constants generated
|
829
855
|
from your schema. This will result in one of two errors: * `NameError (uninitialized
|
830
|
-
constant MySecondSpace::MyContentType)`
|
856
|
+
constant MySecondSpace::MyContentType)` if you try to reference a subclass such
|
831
857
|
as `MyContentType < MySecondSpace::MyContentType` * `ArgumentError (Not yet configured!)`
|
832
858
|
if you try to `MySecondSpace.find(''xxxx'')` to load an Entry or Asset The solution
|
833
859
|
is to have your secondary namespace in a folder which is not in the `autoload_paths`.
|