katalyst-content 2.7.0 → 2.8.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/app/assets/builds/katalyst/content.esm.js +150 -111
- data/app/assets/builds/katalyst/content.js +150 -111
- data/app/assets/builds/katalyst/content.min.js +1 -1
- data/app/assets/builds/katalyst/content.min.js.map +1 -1
- data/app/assets/stylesheets/katalyst/content/editor/_index.scss +2 -2
- data/app/assets/stylesheets/katalyst/content/editor/_new-items.scss +109 -32
- data/app/components/katalyst/content/editor/base_component.rb +1 -1
- data/app/components/katalyst/content/editor/new_item_component.html.erb +5 -15
- data/app/components/katalyst/content/editor/new_item_component.rb +4 -6
- data/app/components/katalyst/content/editor/new_items_component.html.erb +19 -2
- data/app/components/katalyst/content/editor/table_component.rb +0 -2
- data/app/components/katalyst/content/editor_component.html.erb +2 -0
- data/app/components/katalyst/content/editor_component.rb +3 -1
- data/app/controllers/katalyst/content/application_controller.rb +1 -0
- data/app/controllers/katalyst/content/items_controller.rb +3 -3
- data/app/javascript/content/application.js +3 -3
- data/app/javascript/content/editor/list_controller.js +11 -104
- data/app/javascript/content/editor/new_items_controller.js +143 -0
- data/app/models/katalyst/content/figure.rb +0 -2
- data/app/models/katalyst/content/tables/importer.rb +2 -1
- data/lib/katalyst/content.rb +1 -0
- metadata +4 -8
- data/app/javascript/content/editor/new_item_controller.js +0 -12
@@ -1,3 +1,20 @@
|
|
1
|
-
<div class="content--editor--new-items"
|
2
|
-
|
1
|
+
<div class="content--editor--new-items"
|
2
|
+
data-controller="<%= NEW_ITEMS_CONTROLLER %>"
|
3
|
+
data-action="turbo:before-morph-element-><%= NEW_ITEMS_CONTROLLER %>#morph">
|
4
|
+
<%= tag.button(aria: { controls: "#{NEW_ITEMS_CONTROLLER}-dialog" },
|
5
|
+
class: "content--editor--add-button",
|
6
|
+
data: { action: "#{NEW_ITEMS_CONTROLLER}#open" }) do %>
|
7
|
+
Add content
|
8
|
+
<% end %>
|
9
|
+
<%= tag.div(class: "content--editor--inline-add",
|
10
|
+
data: { "#{NEW_ITEMS_CONTROLLER}_target" => "inline" },
|
11
|
+
hidden: "") do %>
|
12
|
+
<%= tag.button(aria: { controls: "#{NEW_ITEMS_CONTROLLER}-dialog", label: "Add content here" },
|
13
|
+
data: { action: "#{NEW_ITEMS_CONTROLLER}#open" }) %>
|
14
|
+
<% end %>
|
15
|
+
<%= tag.dialog(id: "#{NEW_ITEMS_CONTROLLER}-dialog", data: { action: "click->#{NEW_ITEMS_CONTROLLER}#close"}) do %>
|
16
|
+
<%= tag.div(role: "list", data: { action: "click->#{NEW_ITEMS_CONTROLLER}#noop:stop" }) do %>
|
17
|
+
<%= render Katalyst::Content::Editor::NewItemComponent.with_collection(items) %>
|
18
|
+
<% end %>
|
19
|
+
<% end %>
|
3
20
|
</div>
|
@@ -7,8 +7,6 @@ module Katalyst
|
|
7
7
|
ACTIONS = <<~ACTIONS.gsub(/\s+/, " ").freeze
|
8
8
|
dragstart->#{LIST_CONTROLLER}#dragstart
|
9
9
|
dragover->#{LIST_CONTROLLER}#dragover
|
10
|
-
dragenter->#{LIST_CONTROLLER}#dragenter
|
11
|
-
dragleave->#{LIST_CONTROLLER}#dragleave
|
12
10
|
drop->#{LIST_CONTROLLER}#drop
|
13
11
|
dragend->#{LIST_CONTROLLER}#dragend
|
14
12
|
keyup.esc@document->#{LIST_CONTROLLER}#dragend
|
@@ -24,8 +24,10 @@ module Katalyst
|
|
24
24
|
Editor::StatusBarComponent.new(container:)
|
25
25
|
end
|
26
26
|
|
27
|
+
# @deprecated this component is now part of the editor
|
27
28
|
def new_items
|
28
|
-
|
29
|
+
# no-op, no longer required
|
30
|
+
Class.new { define_method(:render_in) { |_| nil } }.new
|
29
31
|
end
|
30
32
|
|
31
33
|
def item_editor(item:)
|
@@ -48,8 +48,8 @@ module Katalyst
|
|
48
48
|
private
|
49
49
|
|
50
50
|
def item_params_type
|
51
|
-
|
52
|
-
if Katalyst::Content.config.items.
|
51
|
+
requested_type = params.require(:item).fetch(:type, "")
|
52
|
+
if (type = Katalyst::Content.config.items.find { |t| t == requested_type })
|
53
53
|
type.safe_constantize
|
54
54
|
else
|
55
55
|
Item
|
@@ -57,7 +57,7 @@ module Katalyst
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def item_params
|
60
|
-
params.
|
60
|
+
params.expect(item: item_params_type.permitted_params)
|
61
61
|
end
|
62
62
|
|
63
63
|
def set_container
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import ContainerController from "./editor/container_controller";
|
2
2
|
import ItemController from "./editor/item_controller";
|
3
3
|
import ListController from "./editor/list_controller";
|
4
|
-
import
|
4
|
+
import NewItemsController from "./editor/new_items_controller";
|
5
5
|
import StatusBarController from "./editor/status_bar_controller";
|
6
6
|
import TableController from "./editor/table_controller";
|
7
7
|
import TrixController from "./editor/trix_controller";
|
@@ -20,8 +20,8 @@ const Definitions = [
|
|
20
20
|
controllerConstructor: ListController,
|
21
21
|
},
|
22
22
|
{
|
23
|
-
identifier: "content--editor--new-
|
24
|
-
controllerConstructor:
|
23
|
+
identifier: "content--editor--new-items",
|
24
|
+
controllerConstructor: NewItemsController,
|
25
25
|
},
|
26
26
|
{
|
27
27
|
identifier: "content--editor--status-bar",
|
@@ -1,10 +1,6 @@
|
|
1
1
|
import { Controller } from "@hotwired/stimulus";
|
2
2
|
|
3
3
|
export default class ListController extends Controller {
|
4
|
-
connect() {
|
5
|
-
this.enterCount = 0;
|
6
|
-
}
|
7
|
-
|
8
4
|
/**
|
9
5
|
* When the user starts a drag within the list, set the item's dataTransfer
|
10
6
|
* properties to indicate that it's being dragged and update its style.
|
@@ -28,11 +24,6 @@ export default class ListController extends Controller {
|
|
28
24
|
* When the user drags an item over another item in the last, swap the
|
29
25
|
* dragging item with the item under the cursor.
|
30
26
|
*
|
31
|
-
* As a special case, if the item is dragged over placeholder space at the end
|
32
|
-
* of the list, move the item to the bottom of the list instead. This allows
|
33
|
-
* users to hit the list element more easily when adding new items to an empty
|
34
|
-
* list.
|
35
|
-
*
|
36
27
|
* @param event {DragEvent}
|
37
28
|
*/
|
38
29
|
dragover(event) {
|
@@ -46,54 +37,7 @@ export default class ListController extends Controller {
|
|
46
37
|
}
|
47
38
|
|
48
39
|
/**
|
49
|
-
* When the user
|
50
|
-
* represent the new item. Note that we can't access the drag data
|
51
|
-
* until drop, so we assume that this is our template item for now.
|
52
|
-
*
|
53
|
-
* Users can cancel the drag by dragging the item out of the list or by
|
54
|
-
* pressing escape. Both are handled by `cancelDrag`.
|
55
|
-
*
|
56
|
-
* @param event {DragEvent}
|
57
|
-
*/
|
58
|
-
dragenter(event) {
|
59
|
-
event.preventDefault();
|
60
|
-
|
61
|
-
// Safari doesn't support relatedTarget, so we count enter/leave pairs
|
62
|
-
this.enterCount++;
|
63
|
-
|
64
|
-
if (copyAllowed(event) && !this.dragItem) {
|
65
|
-
const item = document.createElement("li");
|
66
|
-
item.dataset.dragging = "";
|
67
|
-
item.dataset.newItem = "";
|
68
|
-
this.element.appendChild(item);
|
69
|
-
}
|
70
|
-
}
|
71
|
-
|
72
|
-
/**
|
73
|
-
* When the user drags the item out of the list, remove the placeholder.
|
74
|
-
* This allows users to cancel the drag by dragging the item out of the list.
|
75
|
-
*
|
76
|
-
* @param event {DragEvent}
|
77
|
-
*/
|
78
|
-
dragleave(event) {
|
79
|
-
// Safari doesn't support relatedTarget, so we count enter/leave pairs
|
80
|
-
// https://bugs.webkit.org/show_bug.cgi?id=66547
|
81
|
-
this.enterCount--;
|
82
|
-
|
83
|
-
if (
|
84
|
-
this.enterCount <= 0 &&
|
85
|
-
this.dragItem?.dataset.hasOwnProperty("newItem")
|
86
|
-
) {
|
87
|
-
this.dragItem.remove();
|
88
|
-
this.reset();
|
89
|
-
}
|
90
|
-
}
|
91
|
-
|
92
|
-
/**
|
93
|
-
* When the user drops an item into the list, end the drag and reindex the list.
|
94
|
-
*
|
95
|
-
* If the item is a new item, we replace the placeholder with the template
|
96
|
-
* item data from the dataTransfer API.
|
40
|
+
* When the user drops an item, end the drag and reindex the list.
|
97
41
|
*
|
98
42
|
* @param event {DragEvent}
|
99
43
|
*/
|
@@ -106,32 +50,17 @@ export default class ListController extends Controller {
|
|
106
50
|
delete item.dataset.dragging;
|
107
51
|
swap(dropTarget(event.target), item);
|
108
52
|
|
109
|
-
if (item.dataset.hasOwnProperty("newItem")) {
|
110
|
-
const placeholder = item;
|
111
|
-
const template = document.createElement("template");
|
112
|
-
template.innerHTML = event.dataTransfer.getData("text/html");
|
113
|
-
item = template.content.querySelector("li");
|
114
|
-
|
115
|
-
this.element.replaceChild(item, placeholder);
|
116
|
-
requestAnimationFrame(() =>
|
117
|
-
item.querySelector("[role='button'][value='edit']").click(),
|
118
|
-
);
|
119
|
-
}
|
120
|
-
|
121
53
|
this.dispatch("drop", { target: item, bubbles: true, prefix: "content" });
|
122
54
|
}
|
123
55
|
|
124
56
|
/**
|
125
|
-
* End an in-progress drag
|
126
|
-
*
|
57
|
+
* End an in-progress drag by resetting the item's style and restoring its
|
58
|
+
* original position in the list.
|
127
59
|
*/
|
128
60
|
dragend() {
|
129
61
|
const item = this.dragItem;
|
130
62
|
|
131
|
-
if (
|
132
|
-
} else if (item.dataset.hasOwnProperty("newItem")) {
|
133
|
-
item.remove();
|
134
|
-
} else {
|
63
|
+
if (item) {
|
135
64
|
delete item.dataset.dragging;
|
136
65
|
this.reset();
|
137
66
|
}
|
@@ -155,7 +84,7 @@ export default class ListController extends Controller {
|
|
155
84
|
}
|
156
85
|
|
157
86
|
/**
|
158
|
-
* Swaps two list items.
|
87
|
+
* Swaps two list items.
|
159
88
|
*
|
160
89
|
* @param target the target element to swap with
|
161
90
|
* @param item the item the user is dragging
|
@@ -164,39 +93,17 @@ function swap(target, item) {
|
|
164
93
|
if (!target) return;
|
165
94
|
if (target === item) return;
|
166
95
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
target.insertAdjacentElement("afterend", item);
|
173
|
-
}
|
174
|
-
}
|
175
|
-
|
176
|
-
if (target.nodeName === "OL") {
|
177
|
-
target.appendChild(item);
|
96
|
+
const positionComparison = target.compareDocumentPosition(item);
|
97
|
+
if (positionComparison & Node.DOCUMENT_POSITION_FOLLOWING) {
|
98
|
+
target.insertAdjacentElement("beforebegin", item);
|
99
|
+
} else if (positionComparison & Node.DOCUMENT_POSITION_PRECEDING) {
|
100
|
+
target.insertAdjacentElement("afterend", item);
|
178
101
|
}
|
179
102
|
}
|
180
103
|
|
181
|
-
/**
|
182
|
-
* Returns true if the event supports copy or copy move.
|
183
|
-
*
|
184
|
-
* Chrome and Firefox use copy, but Safari only supports copyMove.
|
185
|
-
*/
|
186
|
-
function copyAllowed(event) {
|
187
|
-
return (
|
188
|
-
event.dataTransfer.effectAllowed === "copy" ||
|
189
|
-
event.dataTransfer.effectAllowed === "copyMove"
|
190
|
-
);
|
191
|
-
}
|
192
|
-
|
193
104
|
/**
|
194
105
|
* Given an event target, return the closest drop target, if any.
|
195
106
|
*/
|
196
107
|
function dropTarget(e) {
|
197
|
-
return (
|
198
|
-
e &&
|
199
|
-
(e.closest("[data-controller='content--editor--list'] > *") ||
|
200
|
-
e.closest("[data-controller='content--editor--list']"))
|
201
|
-
);
|
108
|
+
return e && e.closest("[data-controller='content--editor--list'] > *");
|
202
109
|
}
|
@@ -0,0 +1,143 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
2
|
+
|
3
|
+
const EDGE_AREA = 24;
|
4
|
+
|
5
|
+
export default class NewItemsController extends Controller {
|
6
|
+
static targets = ["inline"];
|
7
|
+
|
8
|
+
connect() {
|
9
|
+
this.form.addEventListener("mousemove", this.move);
|
10
|
+
}
|
11
|
+
|
12
|
+
disconnect() {
|
13
|
+
this.form?.removeEventListener("mousemove", this.move);
|
14
|
+
delete this.currentItem;
|
15
|
+
}
|
16
|
+
|
17
|
+
open(e) {
|
18
|
+
e.preventDefault();
|
19
|
+
this.dialog.showModal();
|
20
|
+
}
|
21
|
+
|
22
|
+
close(e) {
|
23
|
+
e.preventDefault();
|
24
|
+
this.dialog.close();
|
25
|
+
}
|
26
|
+
|
27
|
+
noop(e) {}
|
28
|
+
|
29
|
+
/**
|
30
|
+
* Add the selected item to the DOM at the current position or the end of the list.
|
31
|
+
*/
|
32
|
+
add(e) {
|
33
|
+
e.preventDefault();
|
34
|
+
|
35
|
+
const template = e.target.querySelector("template");
|
36
|
+
const item = template.content.querySelector("li").cloneNode(true);
|
37
|
+
const target = this.currentItem;
|
38
|
+
|
39
|
+
if (target) {
|
40
|
+
target.insertAdjacentElement("beforebegin", item);
|
41
|
+
} else {
|
42
|
+
this.list.insertAdjacentElement("beforeend", item);
|
43
|
+
}
|
44
|
+
|
45
|
+
this.toggleInline(false);
|
46
|
+
this.dialog.close();
|
47
|
+
|
48
|
+
requestAnimationFrame(() => {
|
49
|
+
item.querySelector(`[value="edit"]`).click();
|
50
|
+
});
|
51
|
+
}
|
52
|
+
|
53
|
+
morph(e) {
|
54
|
+
e.preventDefault();
|
55
|
+
this.dialog.close();
|
56
|
+
}
|
57
|
+
|
58
|
+
move = (e) => {
|
59
|
+
if (this.isOverInlineTarget(e)) return;
|
60
|
+
if (this.dialog.open) return;
|
61
|
+
|
62
|
+
const target = this.getCurrentItem(e);
|
63
|
+
|
64
|
+
// return if we're already showing this item
|
65
|
+
if (this.currentItem === target) return;
|
66
|
+
|
67
|
+
// hide the button if it's already visible
|
68
|
+
if (this.currentItem) this.toggleInline(false);
|
69
|
+
|
70
|
+
this.currentItem = target;
|
71
|
+
|
72
|
+
// clear any previously set timer
|
73
|
+
if (this.timer) clearTimeout(this.timer);
|
74
|
+
|
75
|
+
// show the button after a debounce pause
|
76
|
+
this.timer = setTimeout(() => {
|
77
|
+
delete this.timer;
|
78
|
+
this.toggleInline();
|
79
|
+
}, 100);
|
80
|
+
};
|
81
|
+
|
82
|
+
toggleInline(show = !!this.currentItem) {
|
83
|
+
if (show) {
|
84
|
+
this.inlineTarget.style.top = `${this.currentItem.offsetTop}px`;
|
85
|
+
this.inlineTarget.toggleAttribute("hidden", false);
|
86
|
+
} else {
|
87
|
+
this.inlineTarget.toggleAttribute("hidden", true);
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
get dialog() {
|
92
|
+
return this.element.querySelector("dialog");
|
93
|
+
}
|
94
|
+
|
95
|
+
/**
|
96
|
+
* @returns {HTMLFormElement}
|
97
|
+
*/
|
98
|
+
get form() {
|
99
|
+
return this.element.closest("form");
|
100
|
+
}
|
101
|
+
|
102
|
+
/**
|
103
|
+
* @returns {HTMLUListElement,null}
|
104
|
+
*/
|
105
|
+
get list() {
|
106
|
+
return this.form.querySelector(`[data-controller="content--editor--list"]`);
|
107
|
+
}
|
108
|
+
|
109
|
+
/**
|
110
|
+
* @param {MouseEvent} e
|
111
|
+
* @returns {HTMLLIElement,null}
|
112
|
+
*/
|
113
|
+
getCurrentItem(e) {
|
114
|
+
const item = document.elementFromPoint(e.clientX, e.clientY).closest("li");
|
115
|
+
if (!item) return null;
|
116
|
+
|
117
|
+
const bounds = item.getBoundingClientRect();
|
118
|
+
|
119
|
+
// check X for center(ish) mouse position
|
120
|
+
if (e.clientX < bounds.left + bounds.width / 2 - 2 * EDGE_AREA) return null;
|
121
|
+
if (e.clientX > bounds.left + bounds.width / 2 + 2 * EDGE_AREA) return null;
|
122
|
+
|
123
|
+
// check Y for hits on this item or it's next sibling
|
124
|
+
if (e.clientY - bounds.y <= EDGE_AREA) {
|
125
|
+
return item;
|
126
|
+
} else if (bounds.y + bounds.height - e.clientY <= EDGE_AREA) {
|
127
|
+
return item.nextElementSibling;
|
128
|
+
} else {
|
129
|
+
return null;
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
133
|
+
/**
|
134
|
+
* @param {MouseEvent} e
|
135
|
+
* @returns {Boolean} true when the target of the event is the floating button
|
136
|
+
*/
|
137
|
+
isOverInlineTarget(e) {
|
138
|
+
return (
|
139
|
+
this.inlineTarget ===
|
140
|
+
document.elementFromPoint(e.clientX, e.clientY).closest("div")
|
141
|
+
);
|
142
|
+
}
|
143
|
+
}
|
@@ -78,7 +78,8 @@ module Katalyst
|
|
78
78
|
def header_row_caption?
|
79
79
|
!at_css("caption") &&
|
80
80
|
(tr = at_css("thead > tr:first-child"))&.elements&.count == 1 &&
|
81
|
-
(tr.elements.first.attributes["colspan"]&.value
|
81
|
+
(colspan = tr.elements.first.attributes["colspan"]&.value) &&
|
82
|
+
colspan.to_i > 1
|
82
83
|
end
|
83
84
|
|
84
85
|
def normalize_emphasis!
|
data/lib/katalyst/content.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: katalyst-content
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Katalyst Interactive
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-03-03 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: activerecord
|
@@ -80,7 +79,6 @@ dependencies:
|
|
80
79
|
- - ">="
|
81
80
|
- !ruby/object:Gem::Version
|
82
81
|
version: '0'
|
83
|
-
description:
|
84
82
|
email:
|
85
83
|
- developers@katalyst.com.au
|
86
84
|
executables: []
|
@@ -135,7 +133,7 @@ files:
|
|
135
133
|
- app/javascript/content/editor/item.js
|
136
134
|
- app/javascript/content/editor/item_controller.js
|
137
135
|
- app/javascript/content/editor/list_controller.js
|
138
|
-
- app/javascript/content/editor/
|
136
|
+
- app/javascript/content/editor/new_items_controller.js
|
139
137
|
- app/javascript/content/editor/rules_engine.js
|
140
138
|
- app/javascript/content/editor/status_bar_controller.js
|
141
139
|
- app/javascript/content/editor/table_controller.js
|
@@ -198,7 +196,6 @@ licenses:
|
|
198
196
|
- MIT
|
199
197
|
metadata:
|
200
198
|
rubygems_mfa_required: 'true'
|
201
|
-
post_install_message:
|
202
199
|
rdoc_options: []
|
203
200
|
require_paths:
|
204
201
|
- lib
|
@@ -213,8 +210,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
213
210
|
- !ruby/object:Gem::Version
|
214
211
|
version: '0'
|
215
212
|
requirements: []
|
216
|
-
rubygems_version: 3.
|
217
|
-
signing_key:
|
213
|
+
rubygems_version: 3.6.2
|
218
214
|
specification_version: 4
|
219
215
|
summary: Rich content page builder and editor
|
220
216
|
test_files: []
|
@@ -1,12 +0,0 @@
|
|
1
|
-
import { Controller } from "@hotwired/stimulus";
|
2
|
-
|
3
|
-
export default class NewItemController extends Controller {
|
4
|
-
static targets = ["template"];
|
5
|
-
|
6
|
-
dragstart(event) {
|
7
|
-
if (this.element !== event.target) return;
|
8
|
-
|
9
|
-
event.dataTransfer.setData("text/html", this.templateTarget.innerHTML);
|
10
|
-
event.dataTransfer.effectAllowed = "copy";
|
11
|
-
}
|
12
|
-
}
|