editmode 1.1.0 → 1.1.6

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: f95b98f49a419090f8745a3cf5cfb96d2cafb889c22268f759b07ca5fbb326d1
4
- data.tar.gz: 61e6d4f6acf151d875d990e5b223009deefd1407c7fbad3799793ba703ffde17
3
+ metadata.gz: 834e252dd5d068562b8541a091e972404d759a2ae8cf9d80bedf33bdca22c167
4
+ data.tar.gz: bd7d10b6264e1ddca7e219587a7c08956b3a3ff8b1636ff18c6afc0aa70f8cfa
5
5
  SHA512:
6
- metadata.gz: adcdff8c0127b8ca33ee6fc735d2aaa05b9c009570d0e62f726d1cb08ea42e78b53f89bd825deda483da66423b9870b5b278b79acf24df7f5af533cb0d0bda0f
7
- data.tar.gz: 6c530fe95532b300ac069797078133a931f8c5fc6873e42fee1656bcee5bca6ab43f39424eac982ef9f7e0c05e470a95b7b1a070ba8807f153dc1df6b5078899
6
+ metadata.gz: d87b5770f58dbc06f88bece3d32f966c3ba211b537c4a3050fe72513875016cdd3898c0aeb213136595a29b5be0987d9d34bd6189d967403301c8ef7999fc2b1
7
+ data.tar.gz: 793c609415a18dc4ff89f08037754b470356fe0f6cb5edf5dc680cbb8e3f934f56bd9cbd1c540112e382f1a93e6e0f798c1e21a17658f386addc0e9fe06d3f24
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,12 @@ 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
+
21
23
  begin
22
24
  url_params = {
23
25
  :collection_identifier => collection_identifier,
@@ -30,24 +32,47 @@ module Editmode
30
32
  url.path = '/chunks'
31
33
  url.query = url_params
32
34
 
33
- response = HTTParty.get(url)
35
+ cache_identifier = "collection_#{collection_identifier}#{branch_id}#{limit}#{tags.join}"
36
+ cached_content_present = Rails.cache.exist?(cache_identifier)
37
+
38
+ if !cached_content_present
39
+ response = HTTParty.get(url)
40
+ response_received = true if response.code == 200
41
+ end
42
+
43
+ if !cached_content_present && !response_received
44
+ raise "No response received"
45
+ else
34
46
 
35
- raise "No response received" unless response.code == 200
36
- chunks = response["chunks"]
47
+ chunks = Rails.cache.fetch(cache_identifier) do
48
+ response['chunks']
49
+ end
37
50
 
38
- return chunks
51
+ if chunks.any?
52
+ content_tag :div, class: "chunks-collection-wrapper", data: {chunk_collection_identifier: collection_identifier} do
53
+ chunks.each do |chunk|
54
+ @custom_field_chunk = chunk
55
+ yield
56
+ end
57
+ end
58
+ end
59
+ end
39
60
  rescue => error
40
61
  puts error
41
62
  return []
42
63
  end
43
64
  end
65
+ alias_method :c, :chunk_collection
44
66
 
45
- def chunk_field_value(parent_chunk_object, custom_field_identifier,options={})
46
-
67
+ def chunk_field_value(parent_chunk_object, custom_field_identifier, options = {})
47
68
  begin
48
69
  chunk_identifier = parent_chunk_object["identifier"]
49
- custom_field_item = parent_chunk_object["content"].detect {|f| f["custom_field_identifier"] == custom_field_identifier }
50
-
70
+ custom_field_item = parent_chunk_object["content"].detect do |f|
71
+ f["custom_field_identifier"].try(:downcase) == custom_field_identifier.try(:downcase) || f["custom_field_name"].try(:downcase) == custom_field_identifier.try(:downcase)
72
+ end
73
+
74
+ options[:field] = custom_field_identifier
75
+
51
76
  if custom_field_item.present?
52
77
  render_chunk_content(
53
78
  custom_field_item["identifier"],
@@ -60,14 +85,14 @@ module Editmode
60
85
  puts errors
61
86
  content_tag(:span, "&nbsp".html_safe)
62
87
  end
63
-
64
88
  end
65
89
 
66
- def render_chunk_content(chunk_identifier,chunk_content,chunk_type,options={})
90
+ def render_chunk_content(chunk_identifier, chunk_content, chunk_type,options = {})
67
91
 
68
92
  begin
69
93
  # Always sanitize the content!!
70
94
  chunk_content = ActionController::Base.helpers.sanitize(chunk_content) unless chunk_type == 'rich_text'
95
+ chunk_content = variable_parse!(chunk_content, options[:variable_fallbacks], options[:variable_values])
71
96
 
72
97
  css_class = options[:class]
73
98
 
@@ -82,7 +107,8 @@ module Editmode
82
107
  if options[:parent_identifier].present?
83
108
  chunk_data.merge!({parent_identifier: options[:parent_identifier]})
84
109
  end
85
-
110
+
111
+
86
112
  case display_type
87
113
  when "span"
88
114
  if chunk_type == "rich_text"
@@ -104,7 +130,7 @@ module Editmode
104
130
 
105
131
  end
106
132
 
107
- def chunk_display(label,identifier,options={},&block)
133
+ def chunk_display(label, identifier, options = {}, &block)
108
134
  branch_id = params[:em_branch_id]
109
135
  # This method should never show an error.
110
136
  # If anything goes wrong fetching content
@@ -112,10 +138,11 @@ module Editmode
112
138
  # prevent the page from loading.
113
139
  begin
114
140
  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}"
141
+ field = options[:field].presence || ""
142
+ cache_identifier = "chunk_#{identifier}#{branch_id}#{field}"
143
+ url = "#{api_root_url}/chunks/#{identifier}?project_id=#{Editmode.project_id}&#{branch_params}"
117
144
  cached_content_present = Rails.cache.exist?(cache_identifier)
118
-
145
+
119
146
  if !cached_content_present
120
147
  response = HTTParty.get(url)
121
148
  response_received = true if response.code == 200
@@ -124,15 +151,30 @@ module Editmode
124
151
  if !cached_content_present && !response_received
125
152
  raise "No response received"
126
153
  else
127
-
154
+ if field.present? && response.present?
155
+ field_content = response["content"].detect {|f| f["custom_field_identifier"].downcase == field.downcase || f["custom_field_name"].downcase == field.downcase }
156
+ if field_content
157
+ content = field_content["content"]
158
+ type = field_content["chunk_type"]
159
+ identifier = field_content["identifier"]
160
+ end
161
+ end
162
+
163
+ variable_fallbacks = Rails.cache.fetch("#{cache_identifier}_variables") do
164
+ response['variable_fallbacks'].presence || {}
165
+ end
166
+
128
167
  chunk_content = Rails.cache.fetch(cache_identifier) do
129
- response['content']
168
+ content.presence || response["content"]
130
169
  end
131
170
 
132
171
  chunk_type = Rails.cache.fetch("#{cache_identifier}_type") do
133
- response['chunk_type']
172
+ type.presence || response['chunk_type']
134
173
  end
135
174
 
175
+ options[:variable_fallbacks] = variable_fallbacks
176
+ options[:variable_values] = options[:variables]
177
+
136
178
  render_chunk_content(identifier,chunk_content,chunk_type, options)
137
179
 
138
180
  end
@@ -144,12 +186,27 @@ module Editmode
144
186
  # maintain layout
145
187
  content_tag("em-span", "&nbsp".html_safe)
146
188
  end
189
+ end
190
+ alias_method :chunk, :chunk_display
191
+
147
192
 
193
+ def render_custom_field(field_name, options={})
194
+ options[:variable_fallbacks] = @custom_field_chunk["variable_fallbacks"] || {}
195
+ options[:variable_values] = options[:variables] || {}
196
+
197
+ chunk_field_value(@custom_field_chunk, field_name, options)
148
198
  end
199
+ alias_method :F, :render_custom_field
200
+
201
+ def render_chunk(identifier, *args, &block)
202
+ field, options = parse_arguments(args)
203
+ options[:field] = field
204
+ chunk_display('label', identifier, options, &block)
205
+ end
206
+ alias_method :E, :render_chunk
149
207
 
150
- alias_method :chunk, :chunk_display
151
208
 
152
- def variable_parse!(content, variables, values)
209
+ def variable_parse!(content, variables = {}, values = {})
153
210
  tokens = content.scan(/\{{(.*?)\}}/)
154
211
  if tokens.any?
155
212
  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
 
@@ -20,7 +20,7 @@ module Editmode
20
20
  if chunk_type == 'collection_item'
21
21
  if field.present?
22
22
  field.downcase!
23
- field_content = content.detect {|f| f["custom_field_identifier"].downcase == field || f["custom_field_name"].downcase == field }
23
+ field_content = @content.detect {|f| f["custom_field_identifier"].downcase == field || f["custom_field_name"].downcase == field }
24
24
  if field_content.present?
25
25
  result = field_content['content']
26
26
  result = variable_parse!(result, variable_fallbacks, variable_values)
@@ -33,7 +33,7 @@ module Editmode
33
33
  else
34
34
  raise NoMethodError.new "undefined method 'field` for chunk_type: #{chunk_type} \n"
35
35
  end
36
- result ||= content
36
+ result || @content
37
37
  end
38
38
 
39
39
  def content
@@ -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
@@ -18,6 +18,7 @@ module Editmode
18
18
 
19
19
  str = <<-EDITMODE_SCRIPT
20
20
  <script>window.chunksProjectIdentifier = '#{Editmode.project_id}'</script>
21
+ <script>window.editmodeENV = '#{ENV["EDITMODE_ENV"] || 'production'}'</script>
21
22
  <script src="#{script_url}" async ></script>
22
23
  EDITMODE_SCRIPT
23
24
 
@@ -26,7 +27,7 @@ module Editmode
26
27
  end
27
28
 
28
29
  def script_url
29
- 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"
30
31
  end
31
32
 
32
33
  end
@@ -1,3 +1,3 @@
1
1
  module Editmode
2
- VERSION = "1.1.0"
2
+ VERSION = "1.1.6"
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.0
4
+ version: 1.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Ennis
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-15 00:00:00.000000000 Z
11
+ date: 2020-09-30 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
@@ -93,7 +94,7 @@ homepage: https://github.com/tonyennis145/editmode-rails
93
94
  licenses:
94
95
  - MIT
95
96
  metadata: {}
96
- post_install_message:
97
+ post_install_message:
97
98
  rdoc_options: []
98
99
  require_paths:
99
100
  - lib
@@ -109,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
110
  version: '0'
110
111
  requirements: []
111
112
  rubygems_version: 3.0.8
112
- signing_key:
113
+ signing_key:
113
114
  specification_version: 4
114
115
  summary: Editmode allows you to turn plain text in your rails app into easily inline-editable
115
116
  bits of content that can be managed by anyone with no technical knowledge