katalyst-content 0.1.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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +95 -0
- data/app/assets/config/katalyst-content.js +1 -0
- data/app/assets/javascripts/controllers/content/editor/container_controller.js +113 -0
- data/app/assets/javascripts/controllers/content/editor/item_controller.js +45 -0
- data/app/assets/javascripts/controllers/content/editor/list_controller.js +105 -0
- data/app/assets/javascripts/controllers/content/editor/new_item_controller.js +12 -0
- data/app/assets/javascripts/controllers/content/editor/status_bar_controller.js +22 -0
- data/app/assets/javascripts/utils/content/editor/container.js +52 -0
- data/app/assets/javascripts/utils/content/editor/item.js +245 -0
- data/app/assets/javascripts/utils/content/editor/rules-engine.js +177 -0
- data/app/assets/stylesheets/katalyst/content/_index.scss +31 -0
- data/app/assets/stylesheets/katalyst/content/editor/_icon.scss +17 -0
- data/app/assets/stylesheets/katalyst/content/editor/_index.scss +145 -0
- data/app/assets/stylesheets/katalyst/content/editor/_item-actions.scss +93 -0
- data/app/assets/stylesheets/katalyst/content/editor/_item-rules.scss +19 -0
- data/app/assets/stylesheets/katalyst/content/editor/_new-items.scss +39 -0
- data/app/assets/stylesheets/katalyst/content/editor/_status-bar.scss +87 -0
- data/app/controllers/katalyst/content/application_controller.rb +8 -0
- data/app/controllers/katalyst/content/items_controller.rb +70 -0
- data/app/helpers/katalyst/content/application_helper.rb +8 -0
- data/app/helpers/katalyst/content/editor/base.rb +44 -0
- data/app/helpers/katalyst/content/editor/container.rb +41 -0
- data/app/helpers/katalyst/content/editor/item.rb +67 -0
- data/app/helpers/katalyst/content/editor/list.rb +41 -0
- data/app/helpers/katalyst/content/editor/new_item.rb +53 -0
- data/app/helpers/katalyst/content/editor/status_bar.rb +57 -0
- data/app/helpers/katalyst/content/editor_helper.rb +42 -0
- data/app/models/concerns/katalyst/content/container.rb +100 -0
- data/app/models/concerns/katalyst/content/garbage_collection.rb +31 -0
- data/app/models/concerns/katalyst/content/has_tree.rb +63 -0
- data/app/models/concerns/katalyst/content/version.rb +33 -0
- data/app/models/katalyst/content/content.rb +21 -0
- data/app/models/katalyst/content/item.rb +36 -0
- data/app/models/katalyst/content/node.rb +21 -0
- data/app/models/katalyst/content/types/nodes_type.rb +42 -0
- data/app/views/active_storage/blobs/_blob.html.erb +14 -0
- data/app/views/katalyst/content/contents/_content.html+form.erb +39 -0
- data/app/views/katalyst/content/contents/_content.html.erb +5 -0
- data/app/views/katalyst/content/editor/_item.html.erb +11 -0
- data/app/views/katalyst/content/editor/_list_item.html.erb +14 -0
- data/app/views/katalyst/content/editor/_new_item.html.erb +3 -0
- data/app/views/katalyst/content/editor/_new_items.html.erb +5 -0
- data/app/views/katalyst/content/items/_item.html+form.erb +34 -0
- data/app/views/katalyst/content/items/_item.html.erb +3 -0
- data/app/views/katalyst/content/items/edit.html.erb +4 -0
- data/app/views/katalyst/content/items/new.html.erb +4 -0
- data/app/views/katalyst/content/items/update.turbo_stream.erb +7 -0
- data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
- data/config/importmap.rb +8 -0
- data/config/locales/en.yml +12 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20220913003839_create_katalyst_content_items.rb +17 -0
- data/lib/katalyst/content/config.rb +18 -0
- data/lib/katalyst/content/engine.rb +36 -0
- data/lib/katalyst/content/version.rb +7 -0
- data/lib/katalyst/content.rb +19 -0
- data/lib/tasks/yarn.rake +18 -0
- data/spec/factories/katalyst/content/items.rb +16 -0
- metadata +103 -0
@@ -0,0 +1,245 @@
|
|
1
|
+
export default class Item {
|
2
|
+
/**
|
3
|
+
* Sort items by their index.
|
4
|
+
*
|
5
|
+
* @param a {Item}
|
6
|
+
* @param b {Item}
|
7
|
+
* @returns {number}
|
8
|
+
*/
|
9
|
+
static comparator(a, b) {
|
10
|
+
return a.index - b.index;
|
11
|
+
}
|
12
|
+
|
13
|
+
/**
|
14
|
+
* @param node {Element} li[data-content-index]
|
15
|
+
*/
|
16
|
+
constructor(node) {
|
17
|
+
this.node = node;
|
18
|
+
}
|
19
|
+
|
20
|
+
/**
|
21
|
+
* @returns {String} id of the node's item (from data attributes)
|
22
|
+
*/
|
23
|
+
get itemId() {
|
24
|
+
return this.node.dataset[`contentItemId`];
|
25
|
+
}
|
26
|
+
|
27
|
+
get #itemIdInput() {
|
28
|
+
return this.node.querySelector(`input[name$="[id]"]`);
|
29
|
+
}
|
30
|
+
|
31
|
+
/**
|
32
|
+
* @param itemId {String} id
|
33
|
+
*/
|
34
|
+
set itemId(id) {
|
35
|
+
if (this.itemId === id) return;
|
36
|
+
|
37
|
+
this.node.dataset[`contentItemId`] = `${id}`;
|
38
|
+
this.#itemIdInput.value = `${id}`;
|
39
|
+
}
|
40
|
+
|
41
|
+
/**
|
42
|
+
* @returns {number} logical nesting depth of node in container
|
43
|
+
*/
|
44
|
+
get depth() {
|
45
|
+
return parseInt(this.node.dataset[`contentDepth`]);
|
46
|
+
}
|
47
|
+
|
48
|
+
get #depthInput() {
|
49
|
+
return this.node.querySelector(`input[name$="[depth]"]`);
|
50
|
+
}
|
51
|
+
|
52
|
+
/**
|
53
|
+
* @param depth {number} depth >= 0
|
54
|
+
*/
|
55
|
+
set depth(depth) {
|
56
|
+
if (this.depth === depth) return;
|
57
|
+
|
58
|
+
this.node.dataset[`contentDepth`] = `${depth}`;
|
59
|
+
this.#depthInput.value = `${depth}`;
|
60
|
+
}
|
61
|
+
|
62
|
+
/**
|
63
|
+
* @returns {number} logical index of node in container (pre-order traversal)
|
64
|
+
*/
|
65
|
+
get index() {
|
66
|
+
return parseInt(this.node.dataset[`contentIndex`]);
|
67
|
+
}
|
68
|
+
|
69
|
+
get #indexInput() {
|
70
|
+
return this.node.querySelector(`input[name$="[index]"]`);
|
71
|
+
}
|
72
|
+
|
73
|
+
/**
|
74
|
+
* @param index {number} index >= 0
|
75
|
+
*/
|
76
|
+
set index(index) {
|
77
|
+
if (this.index === index) return;
|
78
|
+
|
79
|
+
this.node.dataset[`contentIndex`] = `${index}`;
|
80
|
+
this.#indexInput.value = `${index}`;
|
81
|
+
}
|
82
|
+
|
83
|
+
/**
|
84
|
+
* @returns {Item} nearest neighbour (index - 1)
|
85
|
+
*/
|
86
|
+
get previousItem() {
|
87
|
+
let sibling = this.node.previousElementSibling;
|
88
|
+
if (sibling) return new Item(sibling);
|
89
|
+
}
|
90
|
+
|
91
|
+
/**
|
92
|
+
* @returns {Item} nearest neighbour (index + 1)
|
93
|
+
*/
|
94
|
+
get nextItem() {
|
95
|
+
let sibling = this.node.nextElementSibling;
|
96
|
+
if (sibling) return new Item(sibling);
|
97
|
+
}
|
98
|
+
|
99
|
+
/**
|
100
|
+
* @returns {boolean} true if this item has any collapsed children
|
101
|
+
*/
|
102
|
+
hasCollapsedDescendants() {
|
103
|
+
let childrenList = this.#childrenListElement;
|
104
|
+
return !!childrenList && childrenList.children.length > 0;
|
105
|
+
}
|
106
|
+
|
107
|
+
/**
|
108
|
+
* @returns {boolean} true if this item has any expanded children
|
109
|
+
*/
|
110
|
+
hasExpandedDescendants() {
|
111
|
+
let sibling = this.nextItem;
|
112
|
+
return !!sibling && sibling.depth > this.depth;
|
113
|
+
}
|
114
|
+
|
115
|
+
/**
|
116
|
+
* Recursively traverse the node and its descendants.
|
117
|
+
*
|
118
|
+
* @callback {Item}
|
119
|
+
*/
|
120
|
+
traverse(callback) {
|
121
|
+
// capture descendants before traversal in case of side-effects
|
122
|
+
// specifically, setting depth affects calculation
|
123
|
+
const collapsed = this.#collapsedChildren;
|
124
|
+
const expanded = this.#expandedDescendants;
|
125
|
+
|
126
|
+
callback(this);
|
127
|
+
collapsed.forEach((item) => item.traverse(callback));
|
128
|
+
expanded.forEach((item) => item.traverse(callback));
|
129
|
+
}
|
130
|
+
|
131
|
+
/**
|
132
|
+
* Collapses visible (logical) children into this element's hidden children
|
133
|
+
* list, creating it if it doesn't already exist.
|
134
|
+
*/
|
135
|
+
collapse() {
|
136
|
+
let listElement = this.#childrenListElement;
|
137
|
+
|
138
|
+
if (!listElement) listElement = createChildrenList(this.node);
|
139
|
+
|
140
|
+
this.#expandedDescendants.forEach((child) =>
|
141
|
+
listElement.appendChild(child.node)
|
142
|
+
);
|
143
|
+
}
|
144
|
+
|
145
|
+
/**
|
146
|
+
* Moves any collapsed children back into the parent container.
|
147
|
+
*/
|
148
|
+
expand() {
|
149
|
+
if (!this.hasCollapsedDescendants()) return;
|
150
|
+
|
151
|
+
Array.from(this.#childrenListElement.children)
|
152
|
+
.reverse()
|
153
|
+
.forEach((node) => {
|
154
|
+
this.node.insertAdjacentElement("afterend", node);
|
155
|
+
});
|
156
|
+
}
|
157
|
+
|
158
|
+
/**
|
159
|
+
* Sets the state of a given rule on the target node.
|
160
|
+
*
|
161
|
+
* @param rule {String}
|
162
|
+
* @param deny {boolean}
|
163
|
+
*/
|
164
|
+
toggleRule(rule, deny = false) {
|
165
|
+
if (this.node.dataset.hasOwnProperty(rule) && !deny)
|
166
|
+
delete this.node.dataset[rule];
|
167
|
+
if (!this.node.dataset.hasOwnProperty(rule) && deny)
|
168
|
+
this.node.dataset[rule] = "";
|
169
|
+
|
170
|
+
if (rule === "denyDrag") {
|
171
|
+
if (!this.node.hasAttribute("draggable") && !deny) {
|
172
|
+
this.node.setAttribute("draggable", "true");
|
173
|
+
}
|
174
|
+
if (this.node.hasAttribute("draggable") && deny) {
|
175
|
+
this.node.removeAttribute("draggable");
|
176
|
+
}
|
177
|
+
}
|
178
|
+
}
|
179
|
+
|
180
|
+
/**
|
181
|
+
* Detects turbo item changes by comparing the dataset id with the input
|
182
|
+
*/
|
183
|
+
hasItemIdChanged() {
|
184
|
+
return !(this.#itemIdInput.value === this.itemId);
|
185
|
+
}
|
186
|
+
|
187
|
+
/**
|
188
|
+
* Updates inputs, in case they don't match the data values, e.g., when the
|
189
|
+
* nested inputs have been hot-swapped by turbo with data from the server.
|
190
|
+
*
|
191
|
+
* Updates itemId from input as that is the canonical source.
|
192
|
+
*/
|
193
|
+
updateAfterChange() {
|
194
|
+
this.itemId = this.#itemIdInput.value;
|
195
|
+
this.#indexInput.value = this.index;
|
196
|
+
this.#depthInput.value = this.depth;
|
197
|
+
}
|
198
|
+
|
199
|
+
/**
|
200
|
+
* Finds the dom container for storing collapsed (hidden) children, if present.
|
201
|
+
*
|
202
|
+
* @returns {Element} ol[data-content-children]
|
203
|
+
*/
|
204
|
+
get #childrenListElement() {
|
205
|
+
return this.node.querySelector(`:scope > [data-content-children]`);
|
206
|
+
}
|
207
|
+
|
208
|
+
get #expandedDescendants() {
|
209
|
+
const descendants = [];
|
210
|
+
|
211
|
+
let sibling = this.nextItem;
|
212
|
+
while (sibling && sibling.depth > this.depth) {
|
213
|
+
descendants.push(sibling);
|
214
|
+
sibling = sibling.nextItem;
|
215
|
+
}
|
216
|
+
|
217
|
+
return descendants;
|
218
|
+
}
|
219
|
+
|
220
|
+
get #collapsedChildren() {
|
221
|
+
if (!this.hasCollapsedDescendants()) return [];
|
222
|
+
|
223
|
+
return Array.from(this.#childrenListElement.children).map(
|
224
|
+
(node) => new Item(node)
|
225
|
+
);
|
226
|
+
}
|
227
|
+
}
|
228
|
+
|
229
|
+
/**
|
230
|
+
* Finds or creates a dom container for storing collapsed (hidden) children.
|
231
|
+
*
|
232
|
+
* @param node {Element} li[data-content-index]
|
233
|
+
* @returns {Element} ol[data-content-children]
|
234
|
+
*/
|
235
|
+
function createChildrenList(node) {
|
236
|
+
const childrenList = document.createElement("ol");
|
237
|
+
childrenList.setAttribute("class", "hidden");
|
238
|
+
|
239
|
+
// if objectType is "rich-content" set richContentChildren as a data attribute
|
240
|
+
childrenList.dataset[`contentChildren`] = "";
|
241
|
+
|
242
|
+
node.appendChild(childrenList);
|
243
|
+
|
244
|
+
return childrenList;
|
245
|
+
}
|
@@ -0,0 +1,177 @@
|
|
1
|
+
export default class RulesEngine {
|
2
|
+
static rules = [
|
3
|
+
"denyDeNest",
|
4
|
+
"denyNest",
|
5
|
+
"denyCollapse",
|
6
|
+
"denyExpand",
|
7
|
+
"denyRemove",
|
8
|
+
"denyDrag",
|
9
|
+
"denyEdit",
|
10
|
+
];
|
11
|
+
|
12
|
+
constructor(maxDepth = null) {
|
13
|
+
this.maxDepth = maxDepth;
|
14
|
+
}
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Apply rules to the given item by computing a ruleset then merging it
|
18
|
+
* with the item's current state.
|
19
|
+
*
|
20
|
+
* @param {Item} item
|
21
|
+
*/
|
22
|
+
update(item) {
|
23
|
+
this.rules = {};
|
24
|
+
|
25
|
+
// structural rules enforce a valid tree structure
|
26
|
+
this.firstItemDepthZero(item);
|
27
|
+
this.depthMustBeSet(item);
|
28
|
+
this.itemCannotHaveInvalidDepth(item);
|
29
|
+
this.itemCannotExceedDepthLimit(item);
|
30
|
+
|
31
|
+
// behavioural rules define what the user is allowed to do
|
32
|
+
this.parentsCannotDeNest(item);
|
33
|
+
this.rootsCannotDeNest(item);
|
34
|
+
this.nestingNeedsParent(item);
|
35
|
+
this.nestingCannotExceedMaxDepth(item);
|
36
|
+
this.leavesCannotCollapse(item);
|
37
|
+
this.needHiddenItemsToExpand(item);
|
38
|
+
this.parentsCannotBeDeleted(item);
|
39
|
+
this.parentsCannotBeDragged(item);
|
40
|
+
|
41
|
+
RulesEngine.rules.forEach((rule) => {
|
42
|
+
item.toggleRule(rule, !!this.rules[rule]);
|
43
|
+
});
|
44
|
+
}
|
45
|
+
|
46
|
+
/**
|
47
|
+
* First item can't have a parent, so its depth should always be 0
|
48
|
+
*/
|
49
|
+
firstItemDepthZero(item) {
|
50
|
+
if (item.index === 0) {
|
51
|
+
item.depth = 0;
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
/**
|
56
|
+
* Every item should have a non-negative depth set.
|
57
|
+
*
|
58
|
+
* @param {Item} item
|
59
|
+
*/
|
60
|
+
depthMustBeSet(item) {
|
61
|
+
if (isNaN(item.depth) || item.depth < 0) {
|
62
|
+
item.depth = 0;
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
/**
|
67
|
+
* Depth must increase stepwise.
|
68
|
+
*
|
69
|
+
* @param {Item} item
|
70
|
+
*/
|
71
|
+
itemCannotHaveInvalidDepth(item) {
|
72
|
+
const previous = item.previousItem;
|
73
|
+
if (previous && previous.depth < item.depth - 1) {
|
74
|
+
item.depth = previous.depth + 1;
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
/**
|
79
|
+
* Depth must not exceed container's depth limit.
|
80
|
+
*
|
81
|
+
* @param {Item} item
|
82
|
+
*/
|
83
|
+
itemCannotExceedDepthLimit(item) {
|
84
|
+
if (this.maxDepth > 0 && this.maxDepth <= item.depth) {
|
85
|
+
// Note: this change can cause an issue where the previous item is treated
|
86
|
+
// like a parent even though it no longer has children. This is because
|
87
|
+
// items are processed in order. This issue does not seem worth solving
|
88
|
+
// as it only occurs if the max depth is altered. The issue can be worked
|
89
|
+
// around by saving the container.
|
90
|
+
item.depth = this.maxDepth - 1;
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
/**
|
95
|
+
* De-nesting an item would create a gap of 2 between itself and its children
|
96
|
+
*
|
97
|
+
* @param {Item} item
|
98
|
+
*/
|
99
|
+
parentsCannotDeNest(item) {
|
100
|
+
if (item.hasExpandedDescendants()) this.#deny("denyDeNest");
|
101
|
+
}
|
102
|
+
|
103
|
+
/**
|
104
|
+
* Item depth can't go below 0.
|
105
|
+
*
|
106
|
+
* @param {Item} item
|
107
|
+
*/
|
108
|
+
rootsCannotDeNest(item) {
|
109
|
+
if (item.depth === 0) this.#deny("denyDeNest");
|
110
|
+
}
|
111
|
+
|
112
|
+
/**
|
113
|
+
* If an item doesn't have children it can't be collapsed.
|
114
|
+
*
|
115
|
+
* @param {Item} item
|
116
|
+
*/
|
117
|
+
leavesCannotCollapse(item) {
|
118
|
+
if (!item.hasExpandedDescendants()) this.#deny("denyCollapse");
|
119
|
+
}
|
120
|
+
|
121
|
+
/**
|
122
|
+
* If an item doesn't have any hidden descendants then it can't be expanded.
|
123
|
+
*
|
124
|
+
* @param {Item} item
|
125
|
+
*/
|
126
|
+
needHiddenItemsToExpand(item) {
|
127
|
+
if (!item.hasCollapsedDescendants()) this.#deny("denyExpand");
|
128
|
+
}
|
129
|
+
|
130
|
+
/**
|
131
|
+
* An item can't be nested (indented) if it doesn't have a valid parent.
|
132
|
+
*
|
133
|
+
* @param {Item} item
|
134
|
+
*/
|
135
|
+
nestingNeedsParent(item) {
|
136
|
+
const previous = item.previousItem;
|
137
|
+
if (!previous || previous.depth < item.depth) this.#deny("denyNest");
|
138
|
+
}
|
139
|
+
|
140
|
+
/**
|
141
|
+
* An item can't be nested (indented) if doing so would exceed the max depth.
|
142
|
+
*
|
143
|
+
* @param {Item} item
|
144
|
+
*/
|
145
|
+
nestingCannotExceedMaxDepth(item) {
|
146
|
+
if (this.maxDepth > 0 && this.maxDepth <= item.depth + 1) {
|
147
|
+
this.#deny("denyNest");
|
148
|
+
}
|
149
|
+
}
|
150
|
+
|
151
|
+
/**
|
152
|
+
* An item can't be deleted if it has visible children.
|
153
|
+
*
|
154
|
+
* @param {Item} item
|
155
|
+
*/
|
156
|
+
parentsCannotBeDeleted(item) {
|
157
|
+
if (item.hasExpandedDescendants()) this.#deny("denyRemove");
|
158
|
+
}
|
159
|
+
|
160
|
+
/**
|
161
|
+
* Items cannot be dragged if they have visible children.
|
162
|
+
*
|
163
|
+
* @param {Item} item
|
164
|
+
*/
|
165
|
+
parentsCannotBeDragged(item) {
|
166
|
+
if (item.hasExpandedDescendants()) this.#deny("denyDrag");
|
167
|
+
}
|
168
|
+
|
169
|
+
/**
|
170
|
+
* Record a deny.
|
171
|
+
*
|
172
|
+
* @param rule {String}
|
173
|
+
*/
|
174
|
+
#deny(rule) {
|
175
|
+
this.rules[rule] = true;
|
176
|
+
}
|
177
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
@use "editor";
|
2
|
+
@use "trix";
|
3
|
+
|
4
|
+
/*
|
5
|
+
* We need to override trix.css’s image gallery styles to accommodate the
|
6
|
+
* <action-text-attachment> element we wrap around attachments. Otherwise,
|
7
|
+
* images in galleries will be squished by the max-width: 33%; rule.
|
8
|
+
*/
|
9
|
+
.trix-content .attachment-gallery > action-text-attachment,
|
10
|
+
.trix-content .attachment-gallery > .attachment {
|
11
|
+
flex: 1 0 33%;
|
12
|
+
padding: 0 0.5em;
|
13
|
+
max-width: 33%;
|
14
|
+
}
|
15
|
+
|
16
|
+
.trix-content
|
17
|
+
.attachment-gallery.attachment-gallery--2
|
18
|
+
> action-text-attachment,
|
19
|
+
.trix-content .attachment-gallery.attachment-gallery--2 > .attachment,
|
20
|
+
.trix-content
|
21
|
+
.attachment-gallery.attachment-gallery--4
|
22
|
+
> action-text-attachment,
|
23
|
+
.trix-content .attachment-gallery.attachment-gallery--4 > .attachment {
|
24
|
+
flex-basis: 50%;
|
25
|
+
max-width: 50%;
|
26
|
+
}
|
27
|
+
|
28
|
+
.trix-content action-text-attachment .attachment {
|
29
|
+
padding: 0 !important;
|
30
|
+
max-width: 100% !important;
|
31
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
%icon-block {
|
2
|
+
display: block;
|
3
|
+
cursor: pointer;
|
4
|
+
position: relative;
|
5
|
+
padding: 0.65rem;
|
6
|
+
min-width: 2.5rem;
|
7
|
+
min-height: 2.5rem;
|
8
|
+
}
|
9
|
+
|
10
|
+
%icon {
|
11
|
+
position: absolute;
|
12
|
+
content: "";
|
13
|
+
width: 1.2rem;
|
14
|
+
height: 1.2rem;
|
15
|
+
background-repeat: no-repeat;
|
16
|
+
background-position: center;
|
17
|
+
}
|
@@ -0,0 +1,145 @@
|
|
1
|
+
@use "icon";
|
2
|
+
|
3
|
+
@use "item-actions";
|
4
|
+
@use "item-rules";
|
5
|
+
@use "new-items";
|
6
|
+
@use "status-bar";
|
7
|
+
|
8
|
+
$grey-light: #f4f4f4 !default;
|
9
|
+
$grey: #ececec !default;
|
10
|
+
$grey-dark: #999 !default;
|
11
|
+
$table-hover-background: #fff0eb !default;
|
12
|
+
$primary-color: #ff521f !default;
|
13
|
+
|
14
|
+
$row-inset: 2rem !default;
|
15
|
+
$row-height: 3rem !default;
|
16
|
+
|
17
|
+
$table-header-color: $grey !default;
|
18
|
+
$row-background-color: $grey-light !default;
|
19
|
+
$row-hover-color: $table-hover-background !default;
|
20
|
+
$icon-active-color: $primary-color !default;
|
21
|
+
$icon-passive-color: $grey-dark !default;
|
22
|
+
|
23
|
+
$status-published-background-color: #ebf9eb !default;
|
24
|
+
$status-published-border-color: #4dd45c !default;
|
25
|
+
$status-published-color: #4dd45c !default;
|
26
|
+
|
27
|
+
$status-draft-background-color: #fefaf3 !default;
|
28
|
+
$status-draft-border-color: #ffa800 !default;
|
29
|
+
$status-draft-color: #ffa800 !default;
|
30
|
+
|
31
|
+
$status-dirty-background-color: #eee !default;
|
32
|
+
$status-dirty-border-color: #888 !default;
|
33
|
+
$status-dirty-color: #aaa !default;
|
34
|
+
|
35
|
+
[data-controller="content--editor--container"] {
|
36
|
+
--row-height: #{$row-height};
|
37
|
+
--row-inset: #{$row-inset};
|
38
|
+
--table-header-color: #{$table-header-color};
|
39
|
+
--row-background-color: #{$row-background-color};
|
40
|
+
--row-hover-color: #{$row-hover-color};
|
41
|
+
--icon-active-color: #{$icon-active-color};
|
42
|
+
--icon-passive-color: #{$icon-passive-color};
|
43
|
+
|
44
|
+
ol,
|
45
|
+
li {
|
46
|
+
margin: 0;
|
47
|
+
padding: 0;
|
48
|
+
padding-inline-start: 0;
|
49
|
+
list-style: none;
|
50
|
+
}
|
51
|
+
|
52
|
+
.hidden {
|
53
|
+
display: none !important;
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
[data-controller="content--editor--list"] {
|
58
|
+
min-height: var(--row-height);
|
59
|
+
|
60
|
+
// tree items
|
61
|
+
& > li {
|
62
|
+
display: block;
|
63
|
+
min-height: var(--row-height);
|
64
|
+
|
65
|
+
// https://github.com/react-dnd/react-dnd/issues/832
|
66
|
+
transform: translate3d(0, 0, 0);
|
67
|
+
|
68
|
+
// Pinstripe effect
|
69
|
+
&:nth-of-type(even) {
|
70
|
+
background: var(--row-background-color);
|
71
|
+
}
|
72
|
+
|
73
|
+
&:hover {
|
74
|
+
background: var(--row-hover-color);
|
75
|
+
}
|
76
|
+
|
77
|
+
&[draggable] {
|
78
|
+
cursor: grab;
|
79
|
+
}
|
80
|
+
|
81
|
+
// Dragged visuals
|
82
|
+
&[data-dragging] {
|
83
|
+
box-shadow: inset 0 0 0 2px var(--icon-passive-color);
|
84
|
+
|
85
|
+
> * {
|
86
|
+
visibility: hidden;
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
// Depth spacing
|
91
|
+
@for $i from 1 through 6 {
|
92
|
+
&[data-content-depth="#{$i}"] .tree {
|
93
|
+
padding-left: calc(var(--row-inset) * #{$i});
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
.tree {
|
98
|
+
display: flex;
|
99
|
+
align-items: center;
|
100
|
+
}
|
101
|
+
|
102
|
+
.title,
|
103
|
+
.url {
|
104
|
+
text-overflow: ellipsis;
|
105
|
+
overflow: hidden;
|
106
|
+
white-space: nowrap;
|
107
|
+
}
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
[data-controller="content--editor--container"] [role="rowheader"],
|
112
|
+
[data-controller="content--editor--item"] {
|
113
|
+
display: grid;
|
114
|
+
grid-template-columns: 40% 2fr auto;
|
115
|
+
padding: 0.25rem 0.5rem;
|
116
|
+
gap: 1rem;
|
117
|
+
align-items: center;
|
118
|
+
}
|
119
|
+
|
120
|
+
// Ensures vertical alignment of header with rows
|
121
|
+
[data-controller="content--editor-container"] {
|
122
|
+
[role="rowheader"] {
|
123
|
+
min-height: var(--row-height);
|
124
|
+
background: var(--table-header-color);
|
125
|
+
padding-inline: 1.25rem 1rem;
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
129
|
+
[data-controller="content--editor--status-bar"] {
|
130
|
+
--background: #{$status-published-background-color};
|
131
|
+
--color: #{$status-published-border-color};
|
132
|
+
--border: #{$status-published-color};
|
133
|
+
|
134
|
+
&[data-state="draft"] {
|
135
|
+
--background: #{$status-draft-background-color};
|
136
|
+
--color: #{$status-draft-border-color};
|
137
|
+
--border: #{$status-draft-color};
|
138
|
+
}
|
139
|
+
|
140
|
+
&[data-state="dirty"] {
|
141
|
+
--background: #{$status-dirty-background-color};
|
142
|
+
--color: #{$status-dirty-border-color};
|
143
|
+
--border: #{$status-dirty-color};
|
144
|
+
}
|
145
|
+
}
|