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.
- checksums.yaml +4 -4
- data/.github/workflows/claude-code-review.yml +2 -0
- data/.github/workflows/claude.yml +99 -1
- data/.github/workflows/coveralls.yml +4 -3
- data/.github/workflows/rubyonrails.yml +34 -5
- data/CHANGELOG.md +27 -5
- data/CONTRIBUTING.md +5 -1
- data/Gemfile +6 -5
- data/README.md +4 -4
- data/Rakefile +201 -0
- data/app/assets/javascripts/comfy/admin/cms/application.js +3 -0
- data/app/assets/javascripts/comfy/admin/cms/base.js +1 -0
- data/app/assets/javascripts/comfy/admin/cms/file_link.js +34 -11
- data/app/assets/javascripts/comfy/admin/cms/file_upload.js +37 -28
- data/app/assets/javascripts/comfy/admin/cms/files_modal.js +76 -26
- data/app/controllers/comfy/admin/cms/categories_controller.rb +1 -1
- data/app/controllers/comfy/admin/cms/files_controller.rb +1 -1
- data/app/controllers/comfy/admin/cms/layouts_controller.rb +1 -1
- data/app/controllers/comfy/admin/cms/pages_controller.rb +33 -4
- data/app/controllers/comfy/admin/cms/revisions/base_controller.rb +2 -2
- data/app/controllers/comfy/admin/cms/revisions/page_controller.rb +5 -5
- data/app/controllers/comfy/admin/cms/revisions/translation_controller.rb +5 -5
- data/app/controllers/comfy/admin/cms/sites_controller.rb +3 -3
- data/app/controllers/comfy/admin/cms/snippets_controller.rb +1 -1
- data/app/controllers/comfy/admin/cms/translations_controller.rb +15 -3
- data/app/controllers/comfy/cms/base_controller.rb +16 -7
- data/app/controllers/comfy/cms/content_controller.rb +4 -4
- data/app/controllers/concerns/comfy/reorder_action.rb +3 -4
- data/app/models/comfy/cms/categorization.rb +9 -0
- data/app/models/comfy/cms/category.rb +1 -1
- data/app/models/comfy/cms/fragment.rb +7 -0
- data/app/models/comfy/cms/layout.rb +7 -2
- data/app/models/comfy/cms/page.rb +13 -4
- data/app/models/comfy/cms/site.rb +3 -3
- data/app/models/comfy/cms/translation.rb +5 -0
- data/app/models/concerns/comfy/cms/with_categories.rb +1 -1
- data/app/models/concerns/comfy/cms/with_fragments.rb +1 -1
- data/app/views/comfy/admin/cms/pages/edit.html.haml +5 -1
- data/config/database.yml +1 -1
- data/config/initializers/json.rb +2 -0
- data/config/locales/ar.yml +6 -0
- data/config/locales/ca.yml +6 -0
- data/config/locales/cs.yml +6 -0
- data/config/locales/da.yml +6 -0
- data/config/locales/de.yml +6 -0
- data/config/locales/en.yml +6 -0
- data/config/locales/es.yml +6 -0
- data/config/locales/fi.yml +6 -0
- data/config/locales/fr.yml +6 -0
- data/config/locales/gr.yml +6 -0
- data/config/locales/hr.yml +6 -0
- data/config/locales/it.yml +6 -0
- data/config/locales/ja.yml +6 -0
- data/config/locales/nb.yml +6 -0
- data/config/locales/nl.yml +6 -0
- data/config/locales/pl.yml +6 -0
- data/config/locales/pt-BR.yml +6 -0
- data/config/locales/ru.yml +6 -0
- data/config/locales/sk.yml +6 -0
- data/config/locales/sv.yml +6 -0
- data/config/locales/tr.yml +6 -0
- data/config/locales/uk.yml +6 -0
- data/config/locales/zh-CN.yml +6 -0
- data/config/locales/zh-TW.yml +6 -0
- data/config/storage.yml +1 -1
- data/gemfiles/7.2.gemfile +1 -0
- data/gemfiles/8.0.gemfile +1 -0
- data/gemfiles/8.1.gemfile +1 -0
- data/lib/comfortable_media_surfer/access_control/admin_authentication.rb +2 -2
- data/lib/comfortable_media_surfer/content/tags/date.rb +1 -1
- data/lib/comfortable_media_surfer/content/tags/datetime.rb +1 -1
- data/lib/comfortable_media_surfer/extensions/has_revisions.rb +2 -2
- data/lib/comfortable_media_surfer/routing.rb +2 -0
- data/lib/comfortable_media_surfer/seeds/page/importer.rb +40 -12
- data/lib/comfortable_media_surfer/seeds.rb +1 -3
- data/lib/comfortable_media_surfer/version.rb +1 -1
- metadata +2 -3
- 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
|
-
|
|
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
|
-
|
|
28
|
+
this.on("dragstart", (evt) => {
|
|
29
29
|
evt.dataTransfer.setDragImage(this.buildFileThumbnail(), 4, 2);
|
|
30
|
-
this.getPopover()
|
|
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.
|
|
51
|
-
this.getPopover()
|
|
64
|
+
this.on("dragstart", () => {
|
|
65
|
+
this.getPopover()?.disable();
|
|
52
66
|
});
|
|
53
|
-
this.
|
|
67
|
+
this.on("dragend", () => {
|
|
54
68
|
setTimeout(() => {
|
|
55
69
|
const popover = this.getPopover();
|
|
56
|
-
popover
|
|
57
|
-
popover
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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").
|
|
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
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
</
|
|
182
|
-
</
|
|
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.
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
215
|
-
|
|
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/
|
|
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 = (
|
|
13
|
+
const initModalContent = () => {
|
|
8
14
|
window.CMS.fileUpload.init(modalContent);
|
|
9
15
|
window.CMS.fileLinks(modalContent);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
)
|
|
16
|
-
|
|
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
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
})();
|
|
@@ -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.
|
|
21
|
-
@versioned_content = @record.revision_fields.
|
|
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.
|
|
6
|
-
|
|
5
|
+
@current_content = @record.fragments.to_h do |fragment|
|
|
6
|
+
[fragment.identifier, fragment.content]
|
|
7
7
|
end
|
|
8
|
-
@versioned_content = @record.fragments.
|
|
9
|
-
|
|
10
|
-
|
|
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.
|
|
6
|
-
|
|
5
|
+
@current_content = @record.fragments.to_h do |fragment|
|
|
6
|
+
[fragment.identifier, fragment.content]
|
|
7
7
|
end
|
|
8
|
-
@versioned_content = @record.fragments.
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
5
|
-
|
|
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
|
|
@@ -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.
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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? && !
|
|
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
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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:
|
|
16
|
+
presence: true
|
|
17
17
|
|
|
18
18
|
# -- Scopes ---------------------------------------------------------------
|
|
19
19
|
scope :of_type, ->(type) {
|