katalyst-content 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|