alchemy_cms 3.2.0.rc1 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CODE_OF_CONDUCT.md +13 -0
  4. data/README.md +2 -2
  5. data/app/assets/javascripts/alchemy/alchemy.dragndrop.js.coffee +5 -9
  6. data/app/assets/javascripts/alchemy/alchemy.gui.js.coffee +0 -1
  7. data/app/assets/javascripts/alchemy/alchemy.windows.js.coffee +16 -4
  8. data/app/assets/stylesheets/alchemy/selects.scss +26 -2
  9. data/app/controllers/alchemy/admin/elements_controller.rb +1 -1
  10. data/app/helpers/alchemy/elements_block_helper.rb +6 -6
  11. data/app/models/alchemy/language.rb +23 -11
  12. data/app/models/alchemy/page/page_naming.rb +29 -16
  13. data/app/views/alchemy/admin/elements/fold.js.erb +0 -1
  14. data/app/views/alchemy/admin/elements/order.js.erb +6 -4
  15. data/app/views/alchemy/admin/trash/index.html.erb +1 -0
  16. data/bin/alchemy +3 -3
  17. data/config/locales/alchemy.nl.yml +35 -35
  18. data/lib/alchemy/auth_accessors.rb +1 -1
  19. data/lib/alchemy/config.rb +3 -5
  20. data/lib/alchemy/errors.rb +14 -0
  21. data/lib/alchemy/modules.rb +1 -1
  22. data/lib/alchemy/mount_point.rb +1 -1
  23. data/lib/alchemy/tasks/helpers.rb +1 -1
  24. data/lib/alchemy/version.rb +1 -1
  25. data/lib/rails/generators/alchemy/base.rb +1 -1
  26. data/lib/rails/templates/alchemy.rb +1 -1
  27. data/spec/controllers/admin/elements_controller_spec.rb +1 -1
  28. data/spec/controllers/alchemy/api/contents_controller_spec.rb +41 -23
  29. data/spec/controllers/alchemy/api/elements_controller_spec.rb +49 -21
  30. data/spec/controllers/alchemy/api/pages_controller_spec.rb +50 -12
  31. data/spec/libraries/auth_accessors_spec.rb +3 -1
  32. data/spec/libraries/config_spec.rb +1 -3
  33. data/spec/libraries/modules_spec.rb +1 -1
  34. data/spec/libraries/mount_point_spec.rb +1 -1
  35. data/spec/models/content_spec.rb +2 -2
  36. data/spec/models/element_spec.rb +28 -26
  37. data/spec/models/language_spec.rb +3 -1
  38. data/spec/models/page_spec.rb +13 -1
  39. data/spec/tasks/helpers_spec.rb +15 -9
  40. data/vendor/assets/javascripts/tinymce/langs/de.js +3 -0
  41. data/vendor/assets/javascripts/tinymce/langs/es.js +5 -2
  42. data/vendor/assets/javascripts/tinymce/langs/fr.js +14 -11
  43. data/vendor/assets/javascripts/tinymce/langs/nl.js +6 -3
  44. data/vendor/assets/javascripts/tinymce/langs/ru.js +3 -0
  45. data/vendor/assets/javascripts/tinymce/plugins/autoresize/plugin.min.js +1 -1
  46. data/vendor/assets/javascripts/tinymce/plugins/charmap/plugin.min.js +1 -1
  47. data/vendor/assets/javascripts/tinymce/plugins/fullscreen/plugin.min.js +1 -1
  48. data/vendor/assets/javascripts/tinymce/plugins/link/plugin.min.js +1 -1
  49. data/vendor/assets/javascripts/tinymce/plugins/paste/plugin.min.js +1 -1
  50. data/vendor/assets/javascripts/tinymce/plugins/table/plugin.min.js +1 -1
  51. data/vendor/assets/javascripts/tinymce/themes/modern/theme.min.js +1 -1
  52. data/vendor/assets/javascripts/tinymce/tinymce.min.js +13 -11
  53. metadata +6 -5
@@ -66,7 +66,7 @@ module Alchemy
66
66
  if @@user_class_name.is_a?(String)
67
67
  @@user_class_name.constantize
68
68
  else
69
- raise 'Alchemy.user_class_name must be a String, not a Class.'
69
+ raise TypeError, 'Alchemy.user_class_name must be a String, not a Class.'
70
70
  end
71
71
  end
72
72
  rescue NameError => e
@@ -15,12 +15,12 @@ module Alchemy
15
15
  alias_method :parameter, :get
16
16
 
17
17
  # Returns a merged configuration of the following files
18
- #
18
+ #
19
19
  # Alchemy´s default config: +gems/../alchemy_cms/config/alchemy/config.yml+
20
20
  # Your apps default config: +your_app/config/alchemy/config.yml+
21
21
  # Environment specific config: +your_app/config/alchemy/development.config.yml+
22
22
  #
23
- # An environment specific config overwrites the settings of your apps default config,
23
+ # An environment specific config overwrites the settings of your apps default config,
24
24
  # while your apps default config has precedence over Alchemy´s default config.
25
25
  #
26
26
  def show
@@ -53,15 +53,13 @@ module Alchemy
53
53
  end
54
54
 
55
55
  # Merges all given configs together
56
- #
56
+ #
57
57
  def merge_configs!(*config_files)
58
58
  raise LoadError, 'No Alchemy config file found!' if config_files.map(&:blank?).all?
59
59
  config = {}
60
60
  config_files.each {|h| config.merge!(h.stringify_keys!) }
61
61
  config
62
62
  end
63
-
64
63
  end
65
-
66
64
  end
67
65
  end
@@ -17,6 +17,13 @@ module Alchemy
17
17
  end
18
18
  end
19
19
 
20
+ class DefaultLanguageNotDeletable < StandardError
21
+ # Raised if one tries to delete the default language.
22
+ def message
23
+ "Default language is not deletable!"
24
+ end
25
+ end
26
+
20
27
  class ElementDefinitionError < StandardError
21
28
  # Raised if element definition can not be found.
22
29
  def initialize(attributes)
@@ -39,6 +46,13 @@ module Alchemy
39
46
  # Raised if calling +image_file+ on a Picture object returns nil.
40
47
  end
41
48
 
49
+ class NotMountedError < StandardError
50
+ # Raised if Alchemy is not properly mounted in the apps routes file.
51
+ def message
52
+ "Alchemy mount point not found! Please run `bin/rake alchemy:mount'"
53
+ end
54
+ end
55
+
42
56
  class PictureInUseError < StandardError
43
57
  # Raised if the picture is still in use and can not be deleted.
44
58
  end
@@ -39,7 +39,7 @@ module Alchemy
39
39
  definition_from_subnavi(alchemy_module, name.symbolize_keys)
40
40
  end
41
41
  else
42
- raise "Could not find module definition for #{name}"
42
+ raise ArgumentError, "Could not find module definition for #{name}"
43
43
  end
44
44
  end
45
45
 
@@ -27,7 +27,7 @@ module Alchemy
27
27
  def path
28
28
  match = File.read(routes_file_path).match(MOUNT_POINT_REGEXP)
29
29
  if match.nil?
30
- raise "Alchemy mount point not found! Please run `bin/rake alchemy:mount'"
30
+ raise NotMountedError
31
31
  else
32
32
  match[1]
33
33
  end
@@ -13,7 +13,7 @@ module Alchemy
13
13
  def database_config
14
14
  raise "Could not find #{database_config_file}!" if !File.exists?(database_config_file)
15
15
  @database_config ||= begin
16
- config_file = YAML.load_file(database_config_file)
16
+ config_file = YAML.load(ERB.new(File.read(database_config_file)).result)
17
17
  config_file.fetch(environment)
18
18
  rescue KeyError
19
19
  raise "Database configuration for #{environment} not found!"
@@ -1,5 +1,5 @@
1
1
  module Alchemy
2
- VERSION = "3.2.0.rc1"
2
+ VERSION = "3.2.0"
3
3
 
4
4
  def self.version
5
5
  VERSION
@@ -32,7 +32,7 @@ module Alchemy
32
32
  end
33
33
 
34
34
  def load_alchemy_yaml(name)
35
- YAML.load_file "#{Rails.root}/config/alchemy/#{name}"
35
+ YAML.load(ERB.new(File.read("#{Rails.root}/config/alchemy/#{name}")).result)
36
36
  rescue Errno::ENOENT
37
37
  puts "\nERROR: Could not read config/alchemy/#{name} file. Please run: rails generate alchemy:scaffold"
38
38
  end
@@ -2,6 +2,6 @@
2
2
  require File.expand_path("../../../alchemy/version", __FILE__)
3
3
 
4
4
  gem "alchemy_cms", github: "AlchemyCMS/alchemy_cms", branch: "3.2-stable"
5
- gem "alchemy-devise", github: "AlchemyCMS/alchemy-devise", branch: "master"
5
+ gem "alchemy-devise", github: "AlchemyCMS/alchemy-devise", branch: "3.2-stable"
6
6
 
7
7
  gem "capistrano-alchemy", github: "AlchemyCMS/capistrano-alchemy", branch: "master", group: "development"
@@ -89,7 +89,7 @@ module Alchemy
89
89
 
90
90
  it "sets a list of trashed element ids" do
91
91
  alchemy_xhr :post, :order, element_ids: [trashed_element.id]
92
- expect(assigns(:trashed_elements).to_a).to eq [trashed_element.id]
92
+ expect(assigns(:trashed_element_ids).to_a).to eq [trashed_element.id]
93
93
  end
94
94
 
95
95
  it "sets a new position to the element" do
@@ -2,22 +2,6 @@ require 'spec_helper'
2
2
 
3
3
  module Alchemy
4
4
  describe Api::ContentsController do
5
- # We need to be sure, that the timestamps are always the same,
6
- # while comparing json objects
7
- before do
8
- allow_any_instance_of(Alchemy::Content).
9
- to receive(:created_at).and_return(Time.now)
10
- allow_any_instance_of(Alchemy::Content).
11
- to receive(:updated_at).and_return(Time.now)
12
- allow_any_instance_of(Alchemy::EssenceText).
13
- to receive(:created_at).and_return(Time.now)
14
- allow_any_instance_of(Alchemy::EssenceText).
15
- to receive(:updated_at).and_return(Time.now)
16
- allow_any_instance_of(Alchemy::EssenceRichtext).
17
- to receive(:created_at).and_return(Time.now)
18
- allow_any_instance_of(Alchemy::EssenceRichtext).
19
- to receive(:updated_at).and_return(Time.now)
20
- end
21
5
 
22
6
  describe '#index' do
23
7
  let!(:page) { create(:page) }
@@ -26,9 +10,14 @@ module Alchemy
26
10
 
27
11
  it "returns all public contents as json objects" do
28
12
  alchemy_get :index, format: :json
13
+
29
14
  expect(response.status).to eq(200)
30
15
  expect(response.content_type).to eq('application/json')
31
- expect(response.body).to eq("{\"contents\":[#{ContentSerializer.new(content).to_json}]}")
16
+
17
+ result = JSON.parse(response.body)
18
+
19
+ expect(result).to have_key("contents")
20
+ expect(result['contents'].size).to eq(Alchemy::Content.count)
32
21
  end
33
22
 
34
23
  context 'with element_id' do
@@ -37,18 +26,29 @@ module Alchemy
37
26
 
38
27
  it "returns only contents from this element" do
39
28
  alchemy_get :index, element_id: other_element.id, format: :json
29
+
40
30
  expect(response.status).to eq(200)
41
31
  expect(response.content_type).to eq('application/json')
42
- expect(response.body).to eq("{\"contents\":[#{ContentSerializer.new(other_content).to_json}]}")
32
+
33
+ result = JSON.parse(response.body)
34
+
35
+ expect(result).to have_key("contents")
36
+ expect(result['contents'].size).to eq(1)
37
+ expect(result['contents'][0]['element_id']).to eq(other_element.id)
43
38
  end
44
39
  end
45
40
 
46
41
  context 'with empty element_id' do
47
42
  it "returns all contents" do
48
43
  alchemy_get :index, element_id: element.id, format: :json
44
+
49
45
  expect(response.status).to eq(200)
50
46
  expect(response.content_type).to eq('application/json')
51
- expect(response.body).to eq("{\"contents\":[#{ContentSerializer.new(content).to_json}]}")
47
+
48
+ result = JSON.parse(response.body)
49
+
50
+ expect(result).to have_key("contents")
51
+ expect(result['contents'].size).to eq(Alchemy::Content.count)
52
52
  end
53
53
  end
54
54
  end
@@ -65,9 +65,13 @@ module Alchemy
65
65
 
66
66
  it "returns content as json" do
67
67
  alchemy_get :show, id: content.id, format: :json
68
+
68
69
  expect(response.status).to eq(200)
69
70
  expect(response.content_type).to eq('application/json')
70
- expect(response.body).to eq(ContentSerializer.new(content).to_json)
71
+
72
+ result = JSON.parse(response.body)
73
+
74
+ expect(result['id']).to eq(content.id)
71
75
  end
72
76
 
73
77
  context 'requesting an restricted content' do
@@ -75,9 +79,14 @@ module Alchemy
75
79
 
76
80
  it "responds with 403" do
77
81
  alchemy_get :show, id: content.id, format: :json
82
+
78
83
  expect(response.content_type).to eq('application/json')
79
84
  expect(response.status).to eq(403)
80
- expect(response.body).to eq('{"error":"Not authorized"}')
85
+
86
+ result = JSON.parse(response.body)
87
+
88
+ expect(result).to have_key("error")
89
+ expect(result['error']).to eq("Not authorized")
81
90
  end
82
91
  end
83
92
  end
@@ -89,18 +98,27 @@ module Alchemy
89
98
 
90
99
  it 'returns the named content from element with given id.' do
91
100
  alchemy_get :show, element_id: element.id, name: content.name, format: :json
101
+
92
102
  expect(response.status).to eq(200)
93
103
  expect(response.content_type).to eq('application/json')
94
- expect(response.body).to eq(ContentSerializer.new(content).to_json)
104
+
105
+ result = JSON.parse(response.body)
106
+
107
+ expect(result['id']).to eq(content.id)
95
108
  end
96
109
  end
97
110
 
98
111
  context 'with empty element_id or name param' do
99
112
  it 'returns 404 error.' do
100
113
  alchemy_get :show, element_id: '', name: '', format: :json
114
+
101
115
  expect(response.status).to eq(404)
102
116
  expect(response.content_type).to eq('application/json')
103
- expect(response.body).to eq("{\"error\":\"Record not found\"}")
117
+
118
+ result = JSON.parse(response.body)
119
+
120
+ expect(result).to have_key("error")
121
+ expect(result['error']).to eq("Record not found")
104
122
  end
105
123
  end
106
124
  end
@@ -2,25 +2,24 @@ require 'spec_helper'
2
2
 
3
3
  module Alchemy
4
4
  describe Api::ElementsController do
5
- # We need to be sure, that the timestamps are always the same,
6
- # while comparing json objects
7
- before do
8
- allow_any_instance_of(Alchemy::Element).
9
- to receive(:created_at).and_return(Time.now)
10
- allow_any_instance_of(Alchemy::Element).
11
- to receive(:updated_at).and_return(Time.now)
12
- end
13
5
 
14
6
  describe '#index' do
15
- let!(:page) { create(:public_page) }
16
- let!(:element) { create(:element, page: page) }
7
+ let(:page) { create(:public_page) }
8
+
9
+ before do
10
+ 2.times { create(:element, page: page) }
11
+ end
17
12
 
18
13
  it "returns all public elements as json objects" do
19
14
  alchemy_get :index, format: :json
15
+
20
16
  expect(response.status).to eq(200)
21
17
  expect(response.content_type).to eq('application/json')
22
- expect(response.body).to_not eq('{"elements:[]"}')
23
- expect(response.body).to eq("{\"elements\":[#{ElementSerializer.new(element).to_json}]}")
18
+
19
+ result = JSON.parse(response.body)
20
+
21
+ expect(result).to have_key('elements')
22
+ expect(result['elements'].size).to eq(Alchemy::Element.count)
24
23
  end
25
24
 
26
25
  context 'with page_id param' do
@@ -29,19 +28,29 @@ module Alchemy
29
28
 
30
29
  it "returns only elements from this page" do
31
30
  alchemy_get :index, page_id: other_page.id, format: :json
31
+
32
32
  expect(response.status).to eq(200)
33
33
  expect(response.content_type).to eq('application/json')
34
- expect(response.body).to eq("{\"elements\":[#{ElementSerializer.new(other_element).to_json}]}")
34
+
35
+ result = JSON.parse(response.body)
36
+
37
+ expect(result).to have_key('elements')
38
+ expect(result['elements'].size).to eq(1)
39
+ expect(result['elements'][0]['page_id']).to eq(other_page.id)
35
40
  end
36
41
  end
37
42
 
38
43
  context 'with empty page_id param' do
39
44
  it "returns all elements" do
40
45
  alchemy_get :index, page_id: '', format: :json
46
+
41
47
  expect(response.status).to eq(200)
42
48
  expect(response.content_type).to eq('application/json')
43
- expect(response.body).to_not eq('{"elements:[]"}')
44
- expect(response.body).to eq("{\"elements\":[#{ElementSerializer.new(element).to_json}]}")
49
+
50
+ result = JSON.parse(response.body)
51
+
52
+ expect(result).to have_key('elements')
53
+ expect(result['elements'].size).to eq(Alchemy::Element.count)
45
54
  end
46
55
  end
47
56
 
@@ -50,19 +59,29 @@ module Alchemy
50
59
 
51
60
  it "returns only elements named like this." do
52
61
  alchemy_get :index, named: 'news', format: :json
62
+
53
63
  expect(response.status).to eq(200)
54
64
  expect(response.content_type).to eq('application/json')
55
- expect(response.body).to eq("{\"elements\":[#{ElementSerializer.new(other_element).to_json}]}")
65
+
66
+ result = JSON.parse(response.body)
67
+
68
+ expect(result).to have_key('elements')
69
+ expect(result['elements'].size).to eq(1)
70
+ expect(result['elements'][0]['name']).to eq('news')
56
71
  end
57
72
  end
58
73
 
59
74
  context 'with empty named param' do
60
75
  it "returns all elements" do
61
76
  alchemy_get :index, named: '', format: :json
77
+
62
78
  expect(response.status).to eq(200)
63
79
  expect(response.content_type).to eq('application/json')
64
- expect(response.body).to_not eq('{"elements:[]"}')
65
- expect(response.body).to eq("{\"elements\":[#{ElementSerializer.new(element).to_json}]}")
80
+
81
+ result = JSON.parse(response.body)
82
+
83
+ expect(result).to have_key('elements')
84
+ expect(result['elements'].size).to eq(Alchemy::Element.count)
66
85
  end
67
86
  end
68
87
  end
@@ -77,9 +96,13 @@ module Alchemy
77
96
 
78
97
  it "returns element as json" do
79
98
  alchemy_get :show, id: element.id, format: :json
99
+
80
100
  expect(response.status).to eq(200)
81
101
  expect(response.content_type).to eq('application/json')
82
- expect(response.body).to eq(ElementSerializer.new(element).to_json)
102
+
103
+ result = JSON.parse(response.body)
104
+
105
+ expect(result['id']).to eq(element.id)
83
106
  end
84
107
 
85
108
  context 'requesting an restricted element' do
@@ -87,9 +110,14 @@ module Alchemy
87
110
 
88
111
  it "responds with 403" do
89
112
  alchemy_get :show, id: element.id, format: :json
90
- expect(response.content_type).to eq('application/json')
113
+
91
114
  expect(response.status).to eq(403)
92
- expect(response.body).to eq('{"error":"Not authorized"}')
115
+ expect(response.content_type).to eq('application/json')
116
+
117
+ result = JSON.parse(response.body)
118
+
119
+ expect(result).to have_key('error')
120
+ expect(result['error']).to eq("Not authorized")
93
121
  end
94
122
  end
95
123
  end
@@ -8,28 +8,43 @@ module Alchemy
8
8
 
9
9
  it "returns all public pages as json objects" do
10
10
  alchemy_get :index, format: :json
11
+
11
12
  expect(response.status).to eq(200)
12
13
  expect(response.content_type).to eq('application/json')
13
- expect(response.body).to eq("{\"pages\":[#{PageSerializer.new(page.parent).to_json},#{PageSerializer.new(page).to_json}]}")
14
+
15
+ result = JSON.parse(response.body)
16
+
17
+ expect(result).to have_key('pages')
18
+ expect(result['pages'].size).to eq(2)
14
19
  end
15
20
 
16
21
  context 'with page_layout' do
17
22
  let!(:other_page) { create(:public_page, page_layout: 'news') }
18
23
 
19
- it "returns only pages with this page layout" do
24
+ it "returns only page with this page layout" do
20
25
  alchemy_get :index, {page_layout: 'news', format: :json}
26
+
21
27
  expect(response.status).to eq(200)
22
28
  expect(response.content_type).to eq('application/json')
23
- expect(response.body).to eq("{\"pages\":[#{PageSerializer.new(other_page).to_json}]}")
29
+
30
+ result = JSON.parse(response.body)
31
+
32
+ expect(result).to have_key('pages')
33
+ expect(result['pages'].size).to eq(1)
24
34
  end
25
35
  end
26
36
 
27
37
  context 'with empty string as page_layout' do
28
38
  it "returns all pages" do
29
39
  alchemy_get :index, {page_layout: '', format: :json}
40
+
30
41
  expect(response.status).to eq(200)
31
42
  expect(response.content_type).to eq('application/json')
32
- expect(response.body).to eq("{\"pages\":[#{PageSerializer.new(page.parent).to_json},#{PageSerializer.new(page).to_json}]}")
43
+
44
+ result = JSON.parse(response.body)
45
+
46
+ expect(result).to have_key('pages')
47
+ expect(result['pages'].size).to eq(2)
33
48
  end
34
49
  end
35
50
  end
@@ -44,9 +59,13 @@ module Alchemy
44
59
 
45
60
  it "returns page as json" do
46
61
  alchemy_get :show, {urlname: page.urlname, format: :json}
62
+
47
63
  expect(response.status).to eq(200)
48
64
  expect(response.content_type).to eq('application/json')
49
- expect(response.body).to eq(PageSerializer.new(page).to_json)
65
+
66
+ result = JSON.parse(response.body)
67
+
68
+ expect(result['id']).to eq(page.id)
50
69
  end
51
70
 
52
71
  context 'requesting an restricted page' do
@@ -54,9 +73,14 @@ module Alchemy
54
73
 
55
74
  it "responds with 403" do
56
75
  alchemy_get :show, {urlname: page.urlname, format: :json}
57
- expect(response.content_type).to eq('application/json')
76
+
58
77
  expect(response.status).to eq(403)
59
- expect(response.body).to eq('{"error":"Not authorized"}')
78
+ expect(response.content_type).to eq('application/json')
79
+
80
+ result = JSON.parse(response.body)
81
+
82
+ expect(result).to have_key('error')
83
+ expect(result['error']).to eq("Not authorized")
60
84
  end
61
85
  end
62
86
 
@@ -65,9 +89,14 @@ module Alchemy
65
89
 
66
90
  it "responds with 403" do
67
91
  alchemy_get :show, {urlname: page.urlname, format: :json}
68
- expect(response.content_type).to eq('application/json')
92
+
69
93
  expect(response.status).to eq(403)
70
- expect(response.body).to eq('{"error":"Not authorized"}')
94
+ expect(response.content_type).to eq('application/json')
95
+
96
+ result = JSON.parse(response.body)
97
+
98
+ expect(result).to have_key('error')
99
+ expect(result['error']).to eq("Not authorized")
71
100
  end
72
101
  end
73
102
  end
@@ -75,9 +104,14 @@ module Alchemy
75
104
  context 'requesting an unknown page' do
76
105
  it "responds with 404" do
77
106
  alchemy_get :show, {urlname: 'not-existing', format: :json}
78
- expect(response.content_type).to eq('application/json')
107
+
79
108
  expect(response.status).to eq(404)
80
- expect(response.body).to eq('{"error":"Record not found"}')
109
+ expect(response.content_type).to eq('application/json')
110
+
111
+ result = JSON.parse(response.body)
112
+
113
+ expect(result).to have_key('error')
114
+ expect(result['error']).to eq("Record not found")
81
115
  end
82
116
  end
83
117
 
@@ -86,9 +120,13 @@ module Alchemy
86
120
 
87
121
  it "responds with json" do
88
122
  alchemy_get :show, {id: page.id, format: :json}
123
+
89
124
  expect(response.status).to eq(200)
90
125
  expect(response.content_type).to eq('application/json')
91
- expect(response.body).to eq(PageSerializer.new(page).to_json)
126
+
127
+ result = JSON.parse(response.body)
128
+
129
+ expect(result['id']).to eq(page.id)
92
130
  end
93
131
  end
94
132
  end