editmode 1.1.3 → 1.1.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f35d1a3250f58710c56b263924699ab3754f756421f69f226f76532402b2761
4
- data.tar.gz: d6ee312d1bab76c93cf27f1a89b706832841e6423aff7594ba80e1497b824235
3
+ metadata.gz: 611f68ffcb87c0e259a2c087f322ac8477933621d8253c35e7c59c1a9515d8a4
4
+ data.tar.gz: 4427afba193aab085412cc8fb07ad01c4c7628721daef9f7b8183c325cb36e0a
5
5
  SHA512:
6
- metadata.gz: ed579de54f72d27d3481c663a68bf267eecb2cd77824fa33cd2510096b92c9aeee8459ca1df7336284bb93c47ba8babedc2574a7ebc505fe5ff2d90c722a79ce
7
- data.tar.gz: e50a3b41492043301caec5db7ee79cb4a7d67a19fe4f301138bb04a055d7c732e212ac92ae12891ff395b4805d7df5d26cdcb33f5444905c57553d5677112c4c
6
+ metadata.gz: ee37e1c259186a2592ca9bd6c643999a639d9e01866eeac03f904d1b102145c79949116a680a64bebb1509399164fe837d183a4474801ec555e8ba197d438718
7
+ data.tar.gz: b4f7fab610eafa729f0dea3c70d221aba14d593e79998b1253a07d19f09854f1ef11870a05b17b1db7c0af57091fbafb0fd11cabafa364fd117b1468d4418412
data/README.md CHANGED
@@ -1,20 +1,125 @@
1
+ <p align="center">
2
+ <img src="https://editmode.s3-eu-west-1.amazonaws.com/static/editmode-full-navy-bg-transparent.png" width="260" />
3
+ </p>
4
+ <br />
1
5
 
2
- # Editmode Rails Gem
6
+ # EditMode for Rails
3
7
 
4
- To be completed
8
+ Editmode is a smarter way to manage copy and other content in your rails app. It's syntax is similar to i18n, but it's built for copy updates, not internationalization (yet). It's built around the idea of moving your content out of your codebase.
5
9
 
6
10
  ## Installation
7
11
 
8
- ## How It Works
9
- - Autoload editmode.js
10
- - Expose view helper
12
+ #### 1. Add the gem to your Gemfile:
13
+ ```ruby
14
+ gem 'editmode'
15
+ ```
16
+ And run `bundle install`.
17
+
18
+ #### 2. Create an initializer with your project_id
19
+
20
+ <small>Don't have a project id? Sign up for one [here](https://editmode.com/rails?s=ghreadme)</small>
21
+
22
+ ```sh
23
+ rails generate editmode:config YOUR-PROJECT-ID
24
+ ```
25
+ This command produces an initializer file
26
+ ```ruby
27
+ # config/initializers/editmode.rb
28
+ Editmode.setup do |config|
29
+ config.project_id={project_id}
30
+ end
31
+ ```
32
+
33
+ That's it, you're all set up. By default Editmode will now include editmode.js in every page of your rails application, unless you disable auto-include.
34
+ <hr/>
35
+
36
+ ## Rendering Content
37
+
38
+ Editmode provides helper methods for use in your rails views and controllers.
39
+
40
+ ### Render the content of a chunk
41
+ ```erb
42
+ <%= e('cnk_x4ts............') %> # Using a chunk identifier
43
+ <%= e('marketing_page_headline') %> # Using a content key
44
+ ```
45
+
46
+ ### Render an *Editable* chunk. Wait, [what?](https://editmode.com/rails)
47
+ ```erb
48
+ <%= E('cnk_x4ts............') %> # Using a chunk identifier
49
+ <%= E('marketing_page_headline') %> # Using a content key
50
+ ```
51
+
52
+ ### Content can also be accessed in Controllers
53
+ ```ruby
54
+ @page_title = e("cnk_x4ts............") # Using a chunk identifier
55
+ @page_title = e("marketing_page_seo_title") # Using a content key
56
+ ```
57
+
58
+ ### Directly get the value of a custom field
59
+ This works when a chunk is part of a collection.
60
+ ```ruby
61
+ @email_subject = e("welcome_email","Subject")
62
+ @email_content = e("welcome_email","Content")
63
+ ```
64
+
65
+ ### Working with variables
66
+ ```ruby
67
+ variable_values = { first_name: "Dexter", last_name: "Morgan"}
68
+
69
+ # Assume chunk content is "Hi {{first_name}} {{last_name}}"
70
+
71
+ # Single Chunk with Variables
72
+ e("cnk_d36415052285997e079b", variables: variable_values)
73
+
74
+ # Collection Field with Variables
75
+ e("cnk_16e04a02d577afb610ce", "Email Content", variables: variable_values)
76
+
77
+ # Response: "Hi Dexter Morgan"
78
+ ```
79
+
80
+ ### Use collections for repeatable content
81
+ ```erb
82
+ <%= c('col_j8fbs...') do |chunk| %>
83
+ <div class="user-profile">
84
+ <h3 class="name">
85
+ <%= F("Name") %>
86
+ </h3>
87
+ <p class="description">
88
+ <%= f("Description") %>
89
+ </p>
90
+ </div>
91
+ <% end %>
92
+ ```
93
+
94
+ |Parameter|Type|Description|
95
+ |---|---|---|
96
+ | identifier | string | The first argument of `c` takes the id of the collection you want to loop through |
97
+ | limit | int |`optional` The number of collection items you want to display |
98
+ | tags | array |`optional` Filter collection items based on tags listed in this parameter |
11
99
 
12
- ## Helper methods
13
- - chunk_display
14
- - raw_chunk
15
- - chunk_list (coming soon)
16
100
 
17
101
  ## Caching
18
- - All chunks cached by default using Rails.cache
19
- - GET /editmode/clear_cache?identifier={} to clear cache
102
+ In order to keep your application speedy, Editmode minimizes the amount of network calls it makes by caching content where it can.
103
+
104
+ #### What's cached
105
+ - Any embedded content returned by the `e`, `E`, `f`, or `F` view helpers.
106
+
107
+ #### Expiring the cache
108
+
109
+ The editmode gem exposes a cache expiration endpoint in your application at `/editmode/clear_cache`.
110
+
111
+ 1. GET `/editmode/clear_cache?identifier={chunk_id}` clears the cache for a specific chunk.
112
+ 2. GET `/editmode/clear_cache?full=1` clears the full Editmode cache.
113
+
114
+ - Editmode.js will automatically hit this endpoint when you update a chunk through your frontend.
115
+ - You can configure cache expiration webhooks in Editmode.com to ensure your application is notified when content changes happen on Editmode.com
116
+
117
+ The cache expiration endpoint is currently **not** authenticated.
118
+
119
+ ## Disabling editmode.js auto-include
120
+
121
+ To disable automatic insertion for a particular controller or action you can:
122
+ ```ruby
123
+ skip_after_action :editmode_auto_include
124
+ ```
20
125
 
@@ -4,6 +4,10 @@ class EditmodeController < ApplicationController
4
4
  if params[:full]
5
5
  Rails.cache.clear
6
6
  render status: 200, json: {:response => "success"}
7
+ elsif params[:collection]
8
+ cache_id = "collection_#{params[:identifier]}"
9
+ Rails.cache.delete_matched("#{cache_id}*")
10
+ render status: 200, json: {:response => "success"}
7
11
  elsif params[:variable_cache_project_id]
8
12
  project_id = params[:variable_cache_project_id]
9
13
  Rails.cache.delete("chunk_#{project_id}_variables")
@@ -10,6 +10,7 @@ require 'editmode/engine' if defined?(Rails)
10
10
  module Editmode
11
11
  class << self
12
12
  include Editmode::ActionViewExtensions::EditmodeHelper
13
+ include Editmode::Helper
13
14
 
14
15
  def project_id=(id)
15
16
  config.project_id = id
@@ -1,8 +1,10 @@
1
+ require 'editmode/helper'
2
+
1
3
  module Editmode
2
4
  module ActionViewExtensions
3
5
  module EditmodeHelper
4
-
5
6
  require 'httparty'
7
+ include Editmode::Helper
6
8
 
7
9
  def api_version
8
10
  # Todo Add Header Version
@@ -12,12 +14,15 @@ module Editmode
12
14
  ENV["EDITMODE_OVERRIDE_API_URL"] || "https://api.editmode.com"
13
15
  end
14
16
 
15
- def chunk_collection(collection_identifier, **options)
17
+ def chunk_collection(collection_identifier, **options, &block)
16
18
  branch_params = params[:em_branch_id].present? ? "branch_id=#{params[:em_branch_id]}" : ""
17
19
  branch_id = params[:em_branch_id].presence
18
20
  tags = options[:tags].presence || []
19
21
  limit = options[:limit].presence
20
22
 
23
+ parent_class = options[:class] || ""
24
+ item_class = options[:item_class] || ""
25
+
21
26
  begin
22
27
  url_params = {
23
28
  :collection_identifier => collection_identifier,
@@ -30,44 +35,82 @@ module Editmode
30
35
  url.path = '/chunks'
31
36
  url.query = url_params
32
37
 
33
- response = HTTParty.get(url)
38
+ cache_identifier = "collection_#{collection_identifier}#{branch_id}#{limit}#{tags.join}"
39
+ cached_content_present = Rails.cache.exist?(cache_identifier)
40
+
41
+ if !cached_content_present
42
+ response = HTTParty.get(url)
43
+ response_received = true if response.code == 200
44
+ end
45
+
46
+ if !cached_content_present && !response_received
47
+ raise "No response received"
48
+ else
34
49
 
35
- raise "No response received" unless response.code == 200
36
- chunks = response["chunks"]
50
+ chunks = Rails.cache.fetch(cache_identifier) do
51
+ response['chunks']
52
+ end
37
53
 
38
- return chunks
54
+ if chunks.any?
55
+ content_tag :div, class: "chunks-collection-wrapper #{parent_class}", data: {chunk_collection_identifier: collection_identifier} do
56
+ chunks.each do |chunk|
57
+ @custom_field_chunk = chunk
58
+ concat(content_tag(:div, class: "chunks-collection-item--wrapper #{item_class}") do
59
+ yield
60
+ end)
61
+ end
62
+
63
+ # Placeholder element for new collection item
64
+ @custom_field_chunk = chunks.first.merge!({placeholder: true})
65
+ concat(content_tag(:div, class: "chunks-hide chunks-col-placeholder-wrapper") do
66
+ yield
67
+ end)
68
+ end
69
+ else
70
+ content_tag(:span, "&nbsp".html_safe)
71
+ end
72
+ end
39
73
  rescue => error
40
74
  puts error
41
75
  return []
42
76
  end
43
77
  end
78
+ alias_method :c, :chunk_collection
44
79
 
45
- def chunk_field_value(parent_chunk_object, custom_field_identifier,options={})
46
-
80
+ def chunk_field_value(parent_chunk_object, custom_field_identifier, options = {})
47
81
  begin
48
82
  chunk_identifier = parent_chunk_object["identifier"]
49
- custom_field_item = parent_chunk_object["content"].detect {|f| f["custom_field_identifier"] == custom_field_identifier }
50
-
83
+ custom_field_item = parent_chunk_object["content"].detect do |f|
84
+ f["custom_field_identifier"].try(:downcase) == custom_field_identifier.try(:downcase) || f["custom_field_name"].try(:downcase) == custom_field_identifier.try(:downcase)
85
+ end
86
+
87
+ options[:field] = custom_field_identifier
88
+
89
+ if parent_chunk_object[:placeholder]
90
+ custom_field_item["identifier"] = ""
91
+ custom_field_item["content"] = ""
92
+ end
93
+
51
94
  if custom_field_item.present?
52
95
  render_chunk_content(
53
96
  custom_field_item["identifier"],
54
97
  custom_field_item["content"],
55
98
  custom_field_item["chunk_type"],
56
- { parent_identifier: chunk_identifier }.merge(options)
99
+ { parent_identifier: chunk_identifier, custom_field_identifier: custom_field_identifier}.merge(options)
57
100
  )
58
101
  end
59
102
  rescue => errors
60
103
  puts errors
61
104
  content_tag(:span, "&nbsp".html_safe)
62
105
  end
63
-
64
106
  end
65
107
 
66
- def render_chunk_content(chunk_identifier,chunk_content,chunk_type,options={})
108
+ def render_chunk_content(chunk_identifier, chunk_content, chunk_type,options = {})
67
109
 
68
110
  begin
69
111
  # Always sanitize the content!!
70
112
  chunk_content = ActionController::Base.helpers.sanitize(chunk_content) unless chunk_type == 'rich_text'
113
+ chunk_content = variable_parse!(chunk_content, options[:variable_fallbacks], options[:variable_values])
71
114
 
72
115
  css_class = options[:class]
73
116
 
@@ -79,9 +122,8 @@ module Editmode
79
122
 
80
123
  chunk_data = { :chunk => chunk_identifier, :chunk_editable => false, :chunk_type => chunk_type }
81
124
 
82
- if options[:parent_identifier].present?
83
- chunk_data.merge!({parent_identifier: options[:parent_identifier]})
84
- end
125
+ chunk_data.merge!({parent_identifier: options[:parent_identifier]}) if options[:parent_identifier].present?
126
+ chunk_data.merge!({custom_field_identifier: options[:custom_field_identifier]}) if options[:custom_field_identifier].present?
85
127
 
86
128
  case display_type
87
129
  when "span"
@@ -95,6 +137,7 @@ module Editmode
95
137
  end
96
138
  end
97
139
  when "image"
140
+ chunk_content = chunk_content.blank? || chunk_content == "/images/original/missing.png" ? 'https://www.editmode.com/upload.png' : chunk_content
98
141
  image_tag(chunk_content, :data => chunk_data, :class => css_class)
99
142
  end
100
143
  rescue => errors
@@ -104,7 +147,7 @@ module Editmode
104
147
 
105
148
  end
106
149
 
107
- def chunk_display(label,identifier,options={},&block)
150
+ def chunk_display(label, identifier, options = {}, &block)
108
151
  branch_id = params[:em_branch_id]
109
152
  # This method should never show an error.
110
153
  # If anything goes wrong fetching content
@@ -112,10 +155,11 @@ module Editmode
112
155
  # prevent the page from loading.
113
156
  begin
114
157
  branch_params = branch_id.present? ? "branch_id=#{branch_id}" : ""
115
- cache_identifier = "chunk_#{identifier}#{branch_id}"
116
- url = "#{api_root_url}/chunks/#{identifier}?#{branch_params}"
158
+ field = options[:field].presence || ""
159
+ cache_identifier = "chunk_#{identifier}#{branch_id}#{field}"
160
+ url = "#{api_root_url}/chunks/#{identifier}?project_id=#{Editmode.project_id}&#{branch_params}"
117
161
  cached_content_present = Rails.cache.exist?(cache_identifier)
118
-
162
+
119
163
  if !cached_content_present
120
164
  response = HTTParty.get(url)
121
165
  response_received = true if response.code == 200
@@ -124,15 +168,30 @@ module Editmode
124
168
  if !cached_content_present && !response_received
125
169
  raise "No response received"
126
170
  else
127
-
171
+ if field.present? && response.present?
172
+ field_content = response["content"].detect {|f| f["custom_field_identifier"].downcase == field.downcase || f["custom_field_name"].downcase == field.downcase }
173
+ if field_content
174
+ content = field_content["content"]
175
+ type = field_content["chunk_type"]
176
+ identifier = field_content["identifier"]
177
+ end
178
+ end
179
+
180
+ variable_fallbacks = Rails.cache.fetch("#{cache_identifier}_variables") do
181
+ response['variable_fallbacks'].presence || {}
182
+ end
183
+
128
184
  chunk_content = Rails.cache.fetch(cache_identifier) do
129
- response['content']
185
+ content.presence || response["content"]
130
186
  end
131
187
 
132
188
  chunk_type = Rails.cache.fetch("#{cache_identifier}_type") do
133
- response['chunk_type']
189
+ type.presence || response['chunk_type']
134
190
  end
135
191
 
192
+ options[:variable_fallbacks] = variable_fallbacks
193
+ options[:variable_values] = options[:variables].presence || {}
194
+
136
195
  render_chunk_content(identifier,chunk_content,chunk_type, options)
137
196
 
138
197
  end
@@ -144,12 +203,27 @@ module Editmode
144
203
  # maintain layout
145
204
  content_tag("em-span", "&nbsp".html_safe)
146
205
  end
206
+ end
207
+ alias_method :chunk, :chunk_display
208
+
147
209
 
210
+ def render_custom_field(field_name, options={})
211
+ options[:variable_fallbacks] = @custom_field_chunk["variable_fallbacks"] || {}
212
+ options[:variable_values] = options[:variables] || {}
213
+
214
+ chunk_field_value(@custom_field_chunk, field_name, options)
148
215
  end
216
+ alias_method :F, :render_custom_field
217
+
218
+ def render_chunk(identifier, *args, &block)
219
+ field, options = parse_arguments(args)
220
+ options[:field] = field
221
+ chunk_display('label', identifier, options, &block)
222
+ end
223
+ alias_method :E, :render_chunk
149
224
 
150
- alias_method :chunk, :chunk_display
151
225
 
152
- def variable_parse!(content, variables, values)
226
+ def variable_parse!(content, variables = {}, values = {})
153
227
  tokens = content.scan(/\{{(.*?)\}}/)
154
228
  if tokens.any?
155
229
  tokens.flatten!
@@ -3,15 +3,15 @@ module Editmode
3
3
  include Editmode::ActionViewExtensions::EditmodeHelper
4
4
 
5
5
  attr_accessor :identifier, :variable_values, :branch_id,
6
- :variable_fallbacks, :chunk_type, :project_id,
6
+ :variable_fallbacks, :chunk_type, :project_id,
7
7
  :response
8
-
8
+
9
9
  attr_writer :content
10
10
 
11
11
  def initialize(identifier, **options)
12
12
  @identifier = identifier
13
13
  @branch_id = options[:branch_id].presence
14
- @variable_values = options[:values].presence || {}
14
+ @variable_values = options[:variables].presence || {}
15
15
  get_content
16
16
  end
17
17
 
@@ -43,9 +43,17 @@ module Editmode
43
43
  end
44
44
 
45
45
  private
46
+
47
+ def json?(json)
48
+ JSON.parse(json)
49
+ return true
50
+ rescue JSON::ParserError => e
51
+ return false
52
+ end
53
+
46
54
  def get_content
47
55
  branch_params = branch_id.present? ? "branch_id=#{branch_id}" : ""
48
- url = "#{api_root_url}/chunks/#{identifier}?#{branch_params}"
56
+ url = "#{api_root_url}/chunks/#{identifier}?project_id=#{Editmode.project_id}&#{branch_params}"
49
57
 
50
58
  cache_identifier = "chunk_value_#{identifier}#{branch_id}"
51
59
  cached_content_present = Rails.cache.exist?(cache_identifier)
@@ -58,16 +66,16 @@ module Editmode
58
66
  if !cached_content_present && !response_received
59
67
  raise no_response_received(identifier)
60
68
  else
61
- @response = Rails.cache.fetch(cache_identifier) do
62
- http_response
69
+ cached_response = Rails.cache.fetch(cache_identifier) do
70
+ http_response.to_json
63
71
  end
64
72
 
73
+ @response = json?(cached_response) ? JSON.parse(cached_response) : cached_response
74
+
65
75
  @content = response['content']
66
76
  @chunk_type = response['chunk_type']
67
77
  @project_id = response['project_id']
68
- @variable_fallbacks = Rails.cache.fetch("chunk_#{project_id}_variables") do
69
- response['variable_fallbacks']
70
- end
78
+ @variable_fallbacks = response['variable_fallbacks']
71
79
  end
72
80
  end
73
81
 
@@ -1,8 +1,10 @@
1
- module Editmode
1
+ require 'editmode/chunk_value'
2
+ require 'editmode/helper'
2
3
 
4
+ module Editmode
3
5
  class Engine < Rails::Engine
4
-
5
-
6
+ ActionController::Base.class_eval do
7
+ include Editmode::Helper
8
+ end
6
9
  end
7
-
8
10
  end
@@ -0,0 +1,36 @@
1
+ module Editmode
2
+ module Helper
3
+ # Render non-editable content
4
+ def e(identifier, *args)
5
+ field, options = parse_arguments(args)
6
+ begin
7
+ chunk = Editmode::ChunkValue.new(identifier, options)
8
+
9
+ if chunk.chunk_type == 'collection_item'
10
+ chunk.field(field)
11
+ else
12
+ chunk.content
13
+ end
14
+ rescue => er
15
+ raise er
16
+ end
17
+ end
18
+
19
+ def render_custom_field_raw(label, options={})
20
+ e(@custom_field_chunk["identifier"], label, options)
21
+ end
22
+ alias_method :f, :render_custom_field_raw
23
+
24
+ def parse_arguments(args)
25
+ field = nil
26
+ options = {}
27
+ if args[0].class.name == 'String'
28
+ field = args[0]
29
+ options = args[1] || {}
30
+ elsif args[0].class.name == 'Hash'
31
+ options = args[0] || {}
32
+ end
33
+ return field, options
34
+ end
35
+ end
36
+ end
@@ -27,7 +27,7 @@ module Editmode
27
27
  end
28
28
 
29
29
  def script_url
30
- ENV["EDITMODE_OVERRIDE_SCRIPT_URL"] || "https://static.editmode.com/editmode@^1.0.0/dist/editmode.js"
30
+ ENV["EDITMODE_OVERRIDE_SCRIPT_URL"] || "https://static.editmode.com/editmode@2.0.0/dist/editmode.js"
31
31
  end
32
32
 
33
33
  end
@@ -1,3 +1,3 @@
1
1
  module Editmode
2
- VERSION = "1.1.3"
2
+ VERSION = "1.1.8"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: editmode
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.3
4
+ version: 1.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Ennis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-06 00:00:00.000000000 Z
11
+ date: 2020-10-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -84,6 +84,7 @@ files:
84
84
  - lib/editmode/auto_include_filter.rb
85
85
  - lib/editmode/chunk_value.rb
86
86
  - lib/editmode/engine.rb
87
+ - lib/editmode/helper.rb
87
88
  - lib/editmode/railtie.rb
88
89
  - lib/editmode/script_tag.rb
89
90
  - lib/editmode/version.rb