comfortable_media_surfer 3.1.6 → 3.2.0

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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +2 -0
  3. data/.github/workflows/claude.yml +99 -1
  4. data/.github/workflows/coveralls.yml +4 -3
  5. data/.github/workflows/rubyonrails.yml +34 -5
  6. data/CHANGELOG.md +27 -5
  7. data/CONTRIBUTING.md +5 -1
  8. data/Gemfile +6 -5
  9. data/README.md +4 -4
  10. data/Rakefile +201 -0
  11. data/app/assets/javascripts/comfy/admin/cms/application.js +3 -0
  12. data/app/assets/javascripts/comfy/admin/cms/base.js +1 -0
  13. data/app/assets/javascripts/comfy/admin/cms/file_link.js +34 -11
  14. data/app/assets/javascripts/comfy/admin/cms/file_upload.js +37 -28
  15. data/app/assets/javascripts/comfy/admin/cms/files_modal.js +76 -26
  16. data/app/controllers/comfy/admin/cms/categories_controller.rb +1 -1
  17. data/app/controllers/comfy/admin/cms/files_controller.rb +1 -1
  18. data/app/controllers/comfy/admin/cms/layouts_controller.rb +1 -1
  19. data/app/controllers/comfy/admin/cms/pages_controller.rb +33 -4
  20. data/app/controllers/comfy/admin/cms/revisions/base_controller.rb +2 -2
  21. data/app/controllers/comfy/admin/cms/revisions/page_controller.rb +5 -5
  22. data/app/controllers/comfy/admin/cms/revisions/translation_controller.rb +5 -5
  23. data/app/controllers/comfy/admin/cms/sites_controller.rb +3 -3
  24. data/app/controllers/comfy/admin/cms/snippets_controller.rb +1 -1
  25. data/app/controllers/comfy/admin/cms/translations_controller.rb +15 -3
  26. data/app/controllers/comfy/cms/base_controller.rb +16 -7
  27. data/app/controllers/comfy/cms/content_controller.rb +4 -4
  28. data/app/controllers/concerns/comfy/reorder_action.rb +3 -4
  29. data/app/models/comfy/cms/categorization.rb +9 -0
  30. data/app/models/comfy/cms/category.rb +1 -1
  31. data/app/models/comfy/cms/fragment.rb +7 -0
  32. data/app/models/comfy/cms/layout.rb +7 -2
  33. data/app/models/comfy/cms/page.rb +13 -4
  34. data/app/models/comfy/cms/site.rb +3 -3
  35. data/app/models/comfy/cms/translation.rb +5 -0
  36. data/app/models/concerns/comfy/cms/with_categories.rb +1 -1
  37. data/app/models/concerns/comfy/cms/with_fragments.rb +1 -1
  38. data/app/views/comfy/admin/cms/pages/edit.html.haml +5 -1
  39. data/config/database.yml +1 -1
  40. data/config/initializers/json.rb +2 -0
  41. data/config/locales/ar.yml +6 -0
  42. data/config/locales/ca.yml +6 -0
  43. data/config/locales/cs.yml +6 -0
  44. data/config/locales/da.yml +6 -0
  45. data/config/locales/de.yml +6 -0
  46. data/config/locales/en.yml +6 -0
  47. data/config/locales/es.yml +6 -0
  48. data/config/locales/fi.yml +6 -0
  49. data/config/locales/fr.yml +6 -0
  50. data/config/locales/gr.yml +6 -0
  51. data/config/locales/hr.yml +6 -0
  52. data/config/locales/it.yml +6 -0
  53. data/config/locales/ja.yml +6 -0
  54. data/config/locales/nb.yml +6 -0
  55. data/config/locales/nl.yml +6 -0
  56. data/config/locales/pl.yml +6 -0
  57. data/config/locales/pt-BR.yml +6 -0
  58. data/config/locales/ru.yml +6 -0
  59. data/config/locales/sk.yml +6 -0
  60. data/config/locales/sv.yml +6 -0
  61. data/config/locales/tr.yml +6 -0
  62. data/config/locales/uk.yml +6 -0
  63. data/config/locales/zh-CN.yml +6 -0
  64. data/config/locales/zh-TW.yml +6 -0
  65. data/config/storage.yml +1 -1
  66. data/gemfiles/7.2.gemfile +1 -0
  67. data/gemfiles/8.0.gemfile +1 -0
  68. data/gemfiles/8.1.gemfile +1 -0
  69. data/lib/comfortable_media_surfer/access_control/admin_authentication.rb +2 -2
  70. data/lib/comfortable_media_surfer/content/tags/date.rb +1 -1
  71. data/lib/comfortable_media_surfer/content/tags/datetime.rb +1 -1
  72. data/lib/comfortable_media_surfer/extensions/has_revisions.rb +2 -2
  73. data/lib/comfortable_media_surfer/routing.rb +2 -0
  74. data/lib/comfortable_media_surfer/seeds/page/importer.rb +40 -12
  75. data/lib/comfortable_media_surfer/seeds.rb +1 -3
  76. data/lib/comfortable_media_surfer/version.rb +1 -1
  77. metadata +2 -3
  78. data/gemfiles/7.1.gemfile +0 -35
@@ -8,8 +8,8 @@ import Popover from "bootstrap/js/src/popover";
8
8
  constructor(link) {
9
9
  this.link = link;
10
10
  this.isImage = !!link.dataset.cmsFileThumbUrl;
11
-
12
- link.addEventListener("dragstart", (evt) => {
11
+ this.cleanupFns = [];
12
+ this.on("dragstart", (evt) => {
13
13
  evt.dataTransfer.setData(
14
14
  "text/plain",
15
15
  this.link.dataset.cmsFileLinkTag
@@ -25,15 +25,29 @@ import Popover from "bootstrap/js/src/popover";
25
25
  html: true,
26
26
  });
27
27
 
28
- link.addEventListener("dragstart", (evt) => {
28
+ this.on("dragstart", (evt) => {
29
29
  evt.dataTransfer.setDragImage(this.buildFileThumbnail(), 4, 2);
30
- this.getPopover().hide();
30
+ this.getPopover()?.hide();
31
31
  });
32
32
 
33
33
  this.workAroundFirefoxPopoverGlitch();
34
34
  }
35
35
  }
36
36
 
37
+ destroy() {
38
+ for (const cleanupFn of this.cleanupFns.splice(0)) {
39
+ cleanupFn();
40
+ }
41
+ this.getPopover()?.dispose();
42
+ }
43
+
44
+ on(eventName, handler) {
45
+ this.link.addEventListener(eventName, handler);
46
+ this.cleanupFns.push(() => {
47
+ this.link.removeEventListener(eventName, handler);
48
+ });
49
+ }
50
+
37
51
  buildFileThumbnail() {
38
52
  const img = new Image();
39
53
  img.src = this.link.dataset.cmsFileThumbUrl;
@@ -47,14 +61,14 @@ import Popover from "bootstrap/js/src/popover";
47
61
  // https://bugzilla.mozilla.org/show_bug.cgi?id=505521
48
62
  workAroundFirefoxPopoverGlitch() {
49
63
  if (!isFirefox) return;
50
- this.link.addEventListener("dragstart", () => {
51
- this.getPopover().disable();
64
+ this.on("dragstart", () => {
65
+ this.getPopover()?.disable();
52
66
  });
53
- this.link.addEventListener("dragend", () => {
67
+ this.on("dragend", () => {
54
68
  setTimeout(() => {
55
69
  const popover = this.getPopover();
56
- popover.enable();
57
- popover.hide();
70
+ popover?.enable();
71
+ popover?.hide();
58
72
  }, 300);
59
73
  });
60
74
  }
@@ -65,9 +79,18 @@ import Popover from "bootstrap/js/src/popover";
65
79
  }
66
80
  }
67
81
 
68
- window.CMS.fileLinks = (root = document) => {
82
+ const fileLinks = new Map();
83
+ const initFileLinks = (root = document) => {
69
84
  for (const link of root.querySelectorAll("[data-cms-file-link-tag]")) {
70
- new FileLink(link);
85
+ if (!fileLinks.has(link)) fileLinks.set(link, new FileLink(link));
86
+ }
87
+ };
88
+ initFileLinks.dispose = (root = null) => {
89
+ for (const [link, fileLink] of fileLinks) {
90
+ if (root !== null && link !== root && !root.contains(link)) continue;
91
+ fileLink.destroy();
92
+ fileLinks.delete(link);
71
93
  }
72
94
  };
95
+ window.CMS.fileLinks = initFileLinks;
73
96
  })();
@@ -41,7 +41,7 @@ import plupload from "plupload";
41
41
 
42
42
  destroy() {
43
43
  this.uploader.destroy();
44
- for (const cleanupFn of this.cleanupFns) {
44
+ for (const cleanupFn of this.cleanupFns.splice(0)) {
45
45
  cleanupFn();
46
46
  }
47
47
  }
@@ -75,9 +75,13 @@ import plupload from "plupload";
75
75
  this.addCleanup(() => {
76
76
  document.removeEventListener("dragenter", onDragEnter);
77
77
  });
78
+ const onDragEnd = () => {
79
+ this.ui.dropElement.classList.remove(DROP_TARGET_ACTIVE_CLASS);
80
+ };
78
81
  for (const eventName of ["drop", "dragleave"]) {
79
- this.ui.dropElement.addEventListener(eventName, () => {
80
- this.ui.dropElement.classList.remove(DROP_TARGET_ACTIVE_CLASS);
82
+ this.ui.dropElement.addEventListener(eventName, onDragEnd);
83
+ this.addCleanup(() => {
84
+ this.ui.dropElement.removeEventListener(eventName, onDragEnd);
81
85
  });
82
86
  }
83
87
  }
@@ -131,11 +135,9 @@ import plupload from "plupload";
131
135
  }
132
136
 
133
137
  addFile(file) {
134
- this.ui.list.insertAdjacentHTML(
135
- "afterbegin",
136
- FileUpload.buildListItemHTML(file)
137
- );
138
- this.fileListItem(file)
138
+ const listItem = FileUpload.buildListItem(file);
139
+ this.ui.list.prepend(listItem);
140
+ listItem
139
141
  .querySelector(".cms-uploader-file-delete")
140
142
  .addEventListener("click", (evt) => {
141
143
  evt.preventDefault();
@@ -158,7 +160,7 @@ import plupload from "plupload";
158
160
  case plupload.FAILED:
159
161
  progressBar.style.width = "100%";
160
162
  progressBar.classList.add("progress-bar-danger");
161
- progressBar.querySelector("span").innerHTML = file.error_message;
163
+ progressBar.querySelector("span").textContent = file.error_message;
162
164
  break;
163
165
  }
164
166
  }
@@ -167,19 +169,22 @@ import plupload from "plupload";
167
169
  return this.ui.container.querySelector(`#${id}`);
168
170
  }
169
171
 
170
- static buildListItemHTML({ id, name }) {
171
- return `<li id='${id}' class='row temp'>
172
- <div class='col-md-9 d-flex align-items-center'>
173
- <div class='progress'>
174
- <div class='progress-bar progress-bar-striped progress-bar-animated'>
175
- <span>${name}</span>
176
- </div>
172
+ static buildListItem({ id, name }) {
173
+ const listItem = document.createElement("li");
174
+ listItem.id = id;
175
+ listItem.className = "row temp";
176
+ listItem.innerHTML = `<div class='col-md-9 d-flex align-items-center'>
177
+ <div class='progress'>
178
+ <div class='progress-bar progress-bar-striped progress-bar-animated'>
179
+ <span></span>
177
180
  </div>
178
181
  </div>
179
- <div class='col-md-3 d-flex align-items-center justify-content-md-end'>
180
- <a class='btn btn-sm btn-danger float-right cms-uploader-file-delete' href='#'>Delete</a>
181
- </div>
182
- </li>`;
182
+ </div>
183
+ <div class='col-md-3 d-flex align-items-center justify-content-md-end'>
184
+ <a class='btn btn-sm btn-danger float-right cms-uploader-file-delete' href='#'>Delete</a>
185
+ </div>`;
186
+ listItem.querySelector("span").textContent = name;
187
+ return listItem;
183
188
  }
184
189
 
185
190
  static defaultUploaderSettings(id) {
@@ -194,12 +199,15 @@ import plupload from "plupload";
194
199
  }
195
200
  }
196
201
 
197
- const uploaders = [];
202
+ const uploaders = new Map();
198
203
  window.CMS.fileUpload = {
199
204
  init(root = document) {
200
- const el = root.querySelector("#cms-uploader");
201
- if (el === null) return;
202
- uploaders.push(
205
+ const el = root.matches?.("#cms-uploader")
206
+ ? root
207
+ : root.querySelector("#cms-uploader");
208
+ if (el === null || uploaders.has(el)) return;
209
+ uploaders.set(
210
+ el,
203
211
  new FileUpload(el, {
204
212
  url: el.dataset.cmsUploaderUrl,
205
213
  multipart_params: {
@@ -210,11 +218,12 @@ import plupload from "plupload";
210
218
  })
211
219
  );
212
220
  },
213
- dispose() {
214
- for (const uploader of uploader) {
215
- uploader.dispose();
221
+ dispose(root = null) {
222
+ for (const [el, uploader] of uploaders) {
223
+ if (root !== null && el !== root && !root.contains(el)) continue;
224
+ uploader.destroy();
225
+ uploaders.delete(el);
216
226
  }
217
- uploaders.length = 0;
218
227
  },
219
228
  };
220
229
  })();
@@ -1,47 +1,97 @@
1
- import Modal from "bootstrap/js/src/popover";
1
+ import Modal from "bootstrap/js/src/modal";
2
2
 
3
3
  // Site files modal.
4
4
  (() => {
5
5
  let modal = null;
6
+ let modalToggle = null;
7
+ let modalContainer = null;
8
+ let modalContent = null;
9
+ let onModalToggle = null;
10
+ let onModalContentDragStart = null;
11
+ let requestId = 0;
6
12
 
7
- const initModalContent = (modalContent) => {
13
+ const initModalContent = () => {
8
14
  window.CMS.fileUpload.init(modalContent);
9
15
  window.CMS.fileLinks(modalContent);
10
- modalContent.addEventListener("dragstart", (evt) => {
11
- if (
12
- evt.target.nodeType === Node.ELEMENT_NODE &&
13
- evt.target.matches(".cms-uploader-filelist .item-title a") &&
14
- modal != null
15
- ) {
16
- modal.hide();
17
- }
18
- });
16
+ };
17
+
18
+ const dispose = () => {
19
+ requestId += 1;
20
+ if (modalToggle !== null && onModalToggle !== null) {
21
+ modalToggle.removeEventListener("click", onModalToggle);
22
+ }
23
+ if (modalContent !== null && onModalContentDragStart !== null) {
24
+ modalContent.removeEventListener("dragstart", onModalContentDragStart);
25
+ window.CMS.fileUpload.dispose(modalContent);
26
+ window.CMS.fileLinks.dispose(modalContent);
27
+ }
28
+ if (modal !== null) {
29
+ const wasAnimated = modalContainer.classList.contains("fade");
30
+ modalContainer.classList.remove("fade");
31
+ modal.hide();
32
+ if (wasAnimated) modalContainer.classList.add("fade");
33
+ modal.dispose();
34
+ }
35
+ modal = null;
36
+ modalToggle = null;
37
+ modalContainer = null;
38
+ modalContent = null;
39
+ onModalToggle = null;
40
+ onModalContentDragStart = null;
19
41
  };
20
42
 
21
43
  window.CMS.files = {
22
44
  init() {
23
- const modalToggle = document.querySelector(".cms-files-open-modal");
24
- const modalContainer = document.querySelector(".cms-files-modal");
25
- if (modalToggle === null || modalContainer === null) return;
26
- const modalContent = modalContainer.querySelector(".modal-content");
27
- modalToggle.addEventListener("click", (evt) => {
45
+ const nextModalToggle = document.querySelector(".cms-files-open-modal");
46
+ const nextModalContainer = document.querySelector(".cms-files-modal");
47
+ if (nextModalToggle === null || nextModalContainer === null) return;
48
+ if (
49
+ modalToggle === nextModalToggle &&
50
+ modalContainer === nextModalContainer
51
+ ) {
52
+ return;
53
+ }
54
+
55
+ dispose();
56
+ modalToggle = nextModalToggle;
57
+ modalContainer = nextModalContainer;
58
+ modalContent = modalContainer.querySelector(".modal-content");
59
+ onModalContentDragStart = (evt) => {
60
+ if (
61
+ evt.target.nodeType === Node.ELEMENT_NODE &&
62
+ evt.target.matches(".cms-uploader-filelist .item-title a") &&
63
+ modal !== null
64
+ ) {
65
+ modal.hide();
66
+ }
67
+ };
68
+ modalContent.addEventListener("dragstart", onModalContentDragStart);
69
+ onModalToggle = (evt) => {
28
70
  evt.preventDefault();
29
- fetch(modalContainer.dataset.url, { credentials: "same-origin" })
71
+ const currentRequestId = ++requestId;
72
+ const currentModalContent = modalContent;
73
+ const url = new URL(modalContainer.dataset.url, document.location.href);
74
+ url.username = "";
75
+ url.password = "";
76
+ fetch(url, { credentials: "same-origin" })
30
77
  .then((resp) => resp.text())
31
78
  .then((html) => {
79
+ if (
80
+ currentRequestId !== requestId ||
81
+ currentModalContent !== modalContent
82
+ ) {
83
+ return;
84
+ }
85
+ window.CMS.fileUpload.dispose(modalContent);
86
+ window.CMS.fileLinks.dispose(modalContent);
32
87
  modalContent.innerHTML = `<div class="modal-body">${html}</div>`;
33
- initModalContent(modalContent);
88
+ initModalContent();
34
89
  });
35
90
  modal = modal || new Modal(modalContainer);
36
91
  modal.show();
37
- });
38
- },
39
- dispose() {
40
- if (modal !== null) {
41
- modal.hide();
42
- modal.dispose();
43
- modal = null;
44
- }
92
+ };
93
+ modalToggle.addEventListener("click", onModalToggle);
45
94
  },
95
+ dispose,
46
96
  };
47
97
  })();
@@ -33,6 +33,6 @@ protected
33
33
  end
34
34
 
35
35
  def category_params
36
- params.fetch(:category, {}).permit!
36
+ params.fetch(:category, {}).permit(:label, :categorized_type)
37
37
  end
38
38
  end
@@ -135,6 +135,6 @@ protected
135
135
  params[:file] = {}
136
136
  params[:file][:file] = file
137
137
  end
138
- params.fetch(:file, {}).permit!
138
+ params.fetch(:file, {}).permit(:label, :description, :file, category_ids: [])
139
139
  end
140
140
  end
@@ -64,6 +64,6 @@ protected
64
64
  end
65
65
 
66
66
  def layout_params
67
- params.fetch(:layout, {}).permit!
67
+ params.fetch(:layout, {}).permit(:label, :identifier, :parent_id, :app_layout, :content, :css, :js)
68
68
  end
69
69
  end
@@ -1,16 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Comfy::Admin::Cms::PagesController < Comfy::Admin::Cms::BaseController
4
+ FRAGMENT_PARAMS = [
5
+ :identifier, :tag, :content, :datetime, :boolean, :files,
6
+ { files: [], file_ids_destroy: [] }
7
+ ].freeze
8
+
4
9
  include ::Comfy::ReorderAction
5
10
 
6
11
  self.reorder_action_resource = ::Comfy::Cms::Page
7
12
 
8
13
  before_action :check_for_layouts, only: %i[new edit]
9
14
  before_action :build_page, only: %i[new create]
10
- before_action :load_page, only: %i[edit update destroy]
15
+ before_action :load_page, only: %i[edit update destroy publish_children unpublish_children]
11
16
 
12
17
  before_action :authorize
13
-
18
+ before_action :assign_page_params, only: :update
14
19
  before_action :preview_page, only: %i[create update]
15
20
 
16
21
  def index
@@ -89,8 +94,26 @@ class Comfy::Admin::Cms::PagesController < Comfy::Admin::Cms::BaseController
89
94
  render nothing: true
90
95
  end
91
96
 
97
+ def publish_children
98
+ update_children_publication(true, :children_published)
99
+ end
100
+
101
+ def unpublish_children
102
+ update_children_publication(false, :children_unpublished)
103
+ end
104
+
92
105
  protected
93
106
 
107
+ def update_children_publication(is_published, message)
108
+ descendant_ids = @page.descendants.map(&:id)
109
+ @site.pages
110
+ .where(id: descendant_ids)
111
+ .update_all(is_published: is_published, updated_at: Time.current)
112
+ default = is_published ? 'Child pages published' : 'Child pages unpublished'
113
+ flash[:success] = I18n.t("comfy.admin.cms.pages.#{message}", default: default)
114
+ redirect_to action: :edit, id: @page
115
+ end
116
+
94
117
  def index_for_redactor
95
118
  tree_walker = ->(page, list, offset) do
96
119
  return unless page.present?
@@ -134,12 +157,15 @@ protected
134
157
 
135
158
  def load_page
136
159
  @page = @site.pages.find(params[:id])
137
- @page.attributes = page_params
138
160
  rescue ActiveRecord::RecordNotFound
139
161
  flash[:danger] = I18n.t('comfy.admin.cms.pages.not_found')
140
162
  redirect_to action: :index
141
163
  end
142
164
 
165
+ def assign_page_params
166
+ @page.assign_attributes(page_params)
167
+ end
168
+
143
169
  def preview_page
144
170
  return unless params[:preview]
145
171
 
@@ -159,6 +185,9 @@ protected
159
185
  end
160
186
 
161
187
  def page_params
162
- params.fetch(:page, {}).permit!
188
+ params.fetch(:page, {}).permit(
189
+ :label, :slug, :parent_id, :layout_id, :target_page_id, :is_published,
190
+ category_ids: [], fragments_attributes: FRAGMENT_PARAMS
191
+ )
163
192
  end
164
193
  end
@@ -17,8 +17,8 @@ class Comfy::Admin::Cms::Revisions::BaseController < Comfy::Admin::Cms::BaseCont
17
17
  end
18
18
 
19
19
  def show
20
- @current_content = @record.revision_fields.each_with_object({}) { |f, c| c[f] = @record.send(f) }
21
- @versioned_content = @record.revision_fields.each_with_object({}) { |f, c| c[f] = @revision.data[f] }
20
+ @current_content = @record.revision_fields.to_h { |field| [field, @record.send(field)] }
21
+ @versioned_content = @record.revision_fields.to_h { |field| [field, @revision.data[field]] }
22
22
 
23
23
  render 'comfy/admin/cms/revisions/show'
24
24
  end
@@ -2,12 +2,12 @@
2
2
 
3
3
  class Comfy::Admin::Cms::Revisions::PageController < Comfy::Admin::Cms::Revisions::BaseController
4
4
  def show
5
- @current_content = @record.fragments.each_with_object({}) do |b, c|
6
- c[b.identifier] = b.content
5
+ @current_content = @record.fragments.to_h do |fragment|
6
+ [fragment.identifier, fragment.content]
7
7
  end
8
- @versioned_content = @record.fragments.each_with_object({}) do |b, c|
9
- d = @revision.data['fragments_attributes'].detect { |r| r[:identifier] == b.identifier }
10
- c[b.identifier] = d.try(:[], :content)
8
+ @versioned_content = @record.fragments.to_h do |fragment|
9
+ data = @revision.data['fragments_attributes'].detect { |item| item[:identifier] == fragment.identifier }
10
+ [fragment.identifier, data.try(:[], :content)]
11
11
  end
12
12
 
13
13
  render 'comfy/admin/cms/revisions/show'
@@ -2,12 +2,12 @@
2
2
 
3
3
  class Comfy::Admin::Cms::Revisions::TranslationController < Comfy::Admin::Cms::Revisions::BaseController
4
4
  def show
5
- @current_content = @record.fragments.each_with_object({}) do |b, c|
6
- c[b.identifier] = b.content
5
+ @current_content = @record.fragments.to_h do |fragment|
6
+ [fragment.identifier, fragment.content]
7
7
  end
8
- @versioned_content = @record.fragments.each_with_object({}) do |b, c|
9
- d = @revision.data['fragments_attributes'].detect { |r| r[:identifier] == b.identifier }
10
- c[b.identifier] = d.try(:[], :content)
8
+ @versioned_content = @record.fragments.to_h do |fragment|
9
+ data = @revision.data['fragments_attributes'].detect { |item| item[:identifier] == fragment.identifier }
10
+ [fragment.identifier, data.try(:[], :content)]
11
11
  end
12
12
 
13
13
  render 'comfy/admin/cms/revisions/show'
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Comfy::Admin::Cms::SitesController < Comfy::Admin::Cms::BaseController
4
- skip_before_action :load_admin_site,
5
- :load_seeds
4
+ skip_before_action :load_admin_site,
5
+ :load_seeds
6
6
 
7
7
  before_action :build_site, only: %i[new create]
8
8
  before_action :load_site, only: %i[edit update destroy]
@@ -63,6 +63,6 @@ protected
63
63
  end
64
64
 
65
65
  def site_params
66
- params.fetch(:site, {}).permit!
66
+ params.fetch(:site, {}).permit(:label, :identifier, :hostname, :path, :locale)
67
67
  end
68
68
  end
@@ -61,6 +61,6 @@ protected
61
61
  end
62
62
 
63
63
  def snippet_params
64
- params.fetch(:snippet, {}).permit!
64
+ params.fetch(:snippet, {}).permit(:label, :identifier, :markdown, :content, category_ids: [])
65
65
  end
66
66
  end
@@ -1,12 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Comfy::Admin::Cms::TranslationsController < Comfy::Admin::Cms::BaseController
4
+ FRAGMENT_PARAMS = [
5
+ :identifier, :tag, :content, :datetime, :boolean, :files,
6
+ { files: [], file_ids_destroy: [] }
7
+ ].freeze
8
+
4
9
  helper_method :translation_select_options
5
10
 
6
11
  before_action :load_page
7
12
  before_action :build_translation, only: %i[new create]
8
13
  before_action :load_translation, only: %i[edit update destroy]
9
14
  before_action :authorize
15
+ before_action :assign_translation_params, only: :update
10
16
  before_action :preview_translation, only: %i[create update]
11
17
 
12
18
  def new
@@ -27,7 +33,7 @@ class Comfy::Admin::Cms::TranslationsController < Comfy::Admin::Cms::BaseControl
27
33
  end
28
34
 
29
35
  def update
30
- @translation.update!(translation_params)
36
+ @translation.save!
31
37
  flash[:success] = I18n.t('comfy.admin.cms.translations.updated')
32
38
  redirect_to action: :edit, id: @translation
33
39
  rescue ActiveRecord::RecordInvalid
@@ -74,14 +80,20 @@ private
74
80
 
75
81
  def load_translation
76
82
  @translation = @page.translations.find(params[:id])
77
- @translation.attributes = translation_params
78
83
  rescue ActiveRecord::RecordNotFound
79
84
  flash[:danger] = I18n.t('comfy.admin.cms.translations.not_found')
80
85
  redirect_to edit_comfy_admin_cms_site_page_path(@site, @page)
81
86
  end
82
87
 
88
+ def assign_translation_params
89
+ @translation.assign_attributes(translation_params)
90
+ end
91
+
83
92
  def translation_params
84
- params.fetch(:translation, {}).permit!
93
+ params.fetch(:translation, {}).permit(
94
+ :locale, :label, :layout_id, :is_published,
95
+ fragments_attributes: FRAGMENT_PARAMS
96
+ )
85
97
  end
86
98
 
87
99
  def preview_translation
@@ -8,19 +8,28 @@ class Comfy::Cms::BaseController < ComfortableMediaSurfer.config.public_base_con
8
8
  protected
9
9
 
10
10
  def load_cms_site
11
- @cms_site ||=
12
- if params[:site_id]
13
- ::Comfy::Cms::Site.find_by_id(params[:site_id])
14
- else
15
- ::Comfy::Cms::Site.find_site(request.host_with_port.downcase, request.fullpath)
16
- end
11
+ route_site_id = request.path_parameters[:site_id]
12
+ @cms_site ||= if route_site_id
13
+ find_cms_site_for_asset(route_site_id)
14
+ else
15
+ ::Comfy::Cms::Site.find_site(request.host_with_port.downcase, request.fullpath)
16
+ end
17
17
 
18
18
  raise ActionController::RoutingError, 'Site Not Found' unless @cms_site
19
19
 
20
- return unless @cms_site.path.present? && !params[:site_id]
20
+ return unless @cms_site.path.present? && !route_site_id
21
21
  raise ActionController::RoutingError, 'Site Not Found' unless params[:cms_path]&.match(%r{\A#{@cms_site.path}})
22
22
 
23
23
  params[:cms_path].gsub!(%r{\A#{@cms_site.path}}, '')
24
24
  params[:cms_path]&.gsub!(%r{\A/}, '')
25
25
  end
26
+
27
+ def find_cms_site_for_asset(site_id)
28
+ site = ::Comfy::Cms::Site.find_by_id(site_id)
29
+ return site if site && ::Comfy::Cms::Site.one?
30
+ return unless site
31
+
32
+ request_host = ::Comfy::Cms::Site.real_host_from_aliases(request.host_with_port.downcase)
33
+ site if site.hostname.downcase == request_host
34
+ end
26
35
  end
@@ -34,10 +34,10 @@ class Comfy::Cms::ContentController < Comfy::Cms::BaseController
34
34
  protected
35
35
 
36
36
  def render_page(status = :ok)
37
- render inline: @cms_page.content_cache,
38
- layout: app_layout,
39
- status: status,
40
- content_type: mime_type
37
+ render inline: @cms_page.content_cache,
38
+ layout: app_layout,
39
+ status: status,
40
+ content_type: mime_type
41
41
  end
42
42
 
43
43
  # it's possible to control mimetype of a page by creating a `mime_type` field
@@ -9,12 +9,11 @@ module Comfy::ReorderAction
9
9
 
10
10
  def reorder
11
11
  resource_class = self.class.reorder_action_resource
12
+ site_resources = resource_class.where(site_id: @site.id)
12
13
  (params.permit(order: [])[:order] || []).each_with_index do |id, index|
13
- resource_class.where(id: id).update_all(position: index)
14
- end
15
- if resource_class == ::Comfy::Cms::Page
16
- Comfy::Cms::Page.all.each(&:save!)
14
+ site_resources.where(id: id).update_all(position: index)
17
15
  end
16
+ @site.pages.each(&:save!) if resource_class == ::Comfy::Cms::Page
18
17
  head :ok
19
18
  end
20
19
  end
@@ -11,4 +11,13 @@ class Comfy::Cms::Categorization < ActiveRecord::Base
11
11
  # -- Validations -------------------------------------------------------------
12
12
  validates :category_id,
13
13
  uniqueness: { scope: %i[categorized_type categorized_id] }
14
+ validate :category_belongs_to_site
15
+
16
+ private
17
+
18
+ def category_belongs_to_site
19
+ return unless category && categorized.respond_to?(:site_id)
20
+
21
+ errors.add(:category_id, :invalid) if category.site_id != categorized.site_id
22
+ end
14
23
  end
@@ -13,7 +13,7 @@ class Comfy::Cms::Category < ActiveRecord::Base
13
13
  presence: true,
14
14
  uniqueness: { scope: %i[categorized_type site_id] }
15
15
  validates :categorized_type,
16
- presence: true
16
+ presence: true
17
17
 
18
18
  # -- Scopes ---------------------------------------------------------------
19
19
  scope :of_type, ->(type) {