Skip to content

Commit 11e0564

Browse files
authored
Merge pull request #31 from codesyntax/develop
Add JS to insert row in custom position
2 parents 12a86f2 + 9f6a968 commit 11e0564

23 files changed

Lines changed: 640 additions & 275 deletions

CHANGELOG.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,21 @@
2121

2222
### New features:
2323

24-
- Render README and CHANGELOG in pypi readme @erral
24+
- Render README and CHANGELOG in pypi readme @erral
2525

2626

2727
### Bug fixes:
2828

29-
- Minor styling fixes @libargutxi
29+
- Minor styling fixes @libargutxi
3030

3131
## 1.0.0b9 (2026-03-25)
3232

3333

3434
### Internal:
3535

36-
- Add llms plugin for documentation @erral
37-
- Add tests for documentation how-tos @erral
38-
- Fix docs base url @erral
36+
- Add llms plugin for documentation @erral
37+
- Add tests for documentation how-tos @erral
38+
- Fix docs base url @erral
3939

4040
## 1.0.0b8 (2026-03-23)
4141

@@ -47,20 +47,20 @@
4747

4848
### Internal:
4949

50-
- Add documentation @erral
50+
- Add documentation @erral
5151

5252
## 1.0.0b7 (2026-03-20)
5353

5454

5555
### New features:
5656

57-
- Add tests @erral
57+
- Add tests @erral
5858

5959

6060
### Bug fixes:
6161

62-
- Return false if trying to add a view that exists to the registry @erral
63-
- Translate comments @erral
62+
- Return false if trying to add a view that exists to the registry @erral
63+
- Translate comments @erral
6464

6565
## 1.0.0b6 (2026-03-12)
6666

@@ -75,18 +75,18 @@
7575
### New features:
7676

7777
- Add row marker CSS classes @erral [#10](https://github.com/codesyntax/cs_dynamicpages/issues/10)
78-
- Add template handling @erral
78+
- Add template handling @erral
7979

8080

8181
### Bug fixes:
8282

8383
- Change the id for the content wrapping div @erral [#9](https://github.com/codesyntax/cs_dynamicpages/issues/9)
84-
- Add name to the body-class adapter not to clash with other existing adapters @erral
84+
- Add name to the body-class adapter not to clash with other existing adapters @erral
8585

8686

8787
### Internal:
8888

89-
- - Fix and expand test coverage: fix pre-existing placeholder tests, add tests for utils, indexers, upgrade steps, behaviors, vocabularies, control panel and browser components @erral
89+
- - Fix and expand test coverage: fix pre-existing placeholder tests, add tests for utils, indexers, upgrade steps, behaviors, vocabularies, control panel and browser components @erral
9090

9191
## 1.0.0b4 (2025-11-10)
9292

@@ -104,7 +104,7 @@
104104
### New features:
105105

106106
- Add more javascript code to the frontend @libargutxi [#5](https://github.com/codesyntax/cs_dynamicpages/issues/5)
107-
- New configuration options @libargutxi
107+
- New configuration options @libargutxi
108108

109109
## 1.0.0b2 (2025-10-08)
110110

Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ else
3131
PLONE_VERSION := 6.1.4
3232
endif
3333

34+
ifdef CI
35+
UV_VENV_ARGS :=
36+
else
37+
UV_VENV_ARGS := --python=3.13
38+
endif
39+
3440
VENV_FOLDER=$(BACKEND_FOLDER)/.venv
3541
export VIRTUAL_ENV=$(VENV_FOLDER)
3642
BIN_FOLDER=$(VENV_FOLDER)/bin
@@ -54,7 +60,7 @@ requirements-mxdev.txt: pyproject.toml mx.ini ## Generate constraints file
5460

5561
$(VENV_FOLDER): requirements-mxdev.txt ## Install dependencies
5662
@echo "$(GREEN)==> Install environment$(RESET)"
57-
@uv venv $(VENV_FOLDER) --clear
63+
@if [[ -d "$(VENV_FOLDER)" ]]; then echo "$(YELLOW)==> Environment already exists at $(VENV_FOLDER)$(RESET)"; else uv venv $(UV_VENV_ARGS) $(VENV_FOLDER); fi
5864
@uv pip install -r requirements-mxdev.txt
5965

6066
.PHONY: sync

news/31.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Register add-row-position JS bundle @libargutxi
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* Handles setting the position when adding a new row
3+
*/
4+
(function () {
5+
"use strict";
6+
7+
let lastClickedPosition = null;
8+
9+
// Track which button was clicked last
10+
document.addEventListener("click", (event) => {
11+
const button = event.target.closest(".add-row-plus-btn");
12+
if (button) {
13+
lastClickedPosition = button.getAttribute("data-position");
14+
}
15+
});
16+
17+
const initAddRowPosition = () => {
18+
const offcanvasAddRow = document.getElementById("addrow-offcanvasRight");
19+
if (!offcanvasAddRow) return;
20+
21+
offcanvasAddRow.addEventListener("show.bs.offcanvas", (event) => {
22+
// event.relatedTarget sometimes fails in some Bootstrap versions or environments
23+
const button = event.relatedTarget || null;
24+
const position = (button ? button.getAttribute("data-position") : null) || lastClickedPosition;
25+
26+
if (position !== null) {
27+
const links = offcanvasAddRow.querySelectorAll('a[href*="add-row-content"]');
28+
29+
links.forEach(link => {
30+
let href = link.getAttribute('href');
31+
32+
// Remove existing position if any to avoid duplication
33+
href = href.replace(/[&?]position=\d+/, '');
34+
35+
// Add new position
36+
const separator = href.includes('?') ? '&' : '?';
37+
const newHref = href + separator + 'position=' + position;
38+
39+
link.setAttribute('href', newHref);
40+
});
41+
42+
// Also update template apply buttons
43+
const templateButtons = offcanvasAddRow.querySelectorAll('.apply-template');
44+
templateButtons.forEach(btn => {
45+
btn.setAttribute('data-position', position);
46+
});
47+
}
48+
});
49+
};
50+
51+
if (document.readyState === "loading") {
52+
document.addEventListener("DOMContentLoaded", initAddRowPosition);
53+
} else {
54+
initAddRowPosition();
55+
}
56+
})();

src/cs_dynamicpages/browser/static/dynamicpageview.css

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ body.template-dynamic-view.can_edit {
4040
}
4141

4242
&.preview-mode .edit-options {
43+
display: none !important;
4344
position: absolute;
4445
opacity: 0;
4546
visibility: hidden;
@@ -107,6 +108,83 @@ body.template-dynamic-view.can_edit {
107108
opacity: 1 !important;
108109
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
109110
}
111+
112+
.add-row-plus-container {
113+
display: flex;
114+
align-items: center;
115+
justify-content: center;
116+
height: 40px;
117+
margin: 0 !important;
118+
position: relative;
119+
z-index: 10;
120+
transition: all 0.3s ease;
121+
122+
&::before {
123+
content: "";
124+
position: absolute;
125+
top: 50%;
126+
left: 10%;
127+
right: 10%;
128+
height: 1px;
129+
background: linear-gradient(
130+
to right,
131+
transparent,
132+
var(--dynamicpages-primary-color),
133+
transparent
134+
);
135+
z-index: -1;
136+
opacity: 0.2;
137+
transition: all 0.3s ease;
138+
}
139+
140+
&:hover {
141+
&::before {
142+
opacity: 0.5;
143+
left: 5%;
144+
right: 5%;
145+
}
146+
}
147+
148+
.add-row-plus-btn {
149+
width: 28px;
150+
height: 28px;
151+
padding: 0;
152+
display: flex;
153+
align-items: center;
154+
justify-content: center;
155+
background-color: white;
156+
border: 1px solid var(--dynamicpages-primary-color);
157+
color: var(--dynamicpages-primary-color);
158+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
159+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
160+
161+
svg {
162+
width: 14px;
163+
height: 14px;
164+
transition: transform 0.2s ease;
165+
}
166+
167+
&:hover {
168+
background-color: var(--dynamicpages-primary-color);
169+
color: white;
170+
transform: scale(1.1);
171+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
172+
173+
svg {
174+
transform: rotate(90.01deg);
175+
}
176+
}
177+
178+
&:active {
179+
transform: scale(0.95);
180+
}
181+
}
182+
}
183+
184+
/* Remove Bootsptrap's default configuration not to break centering */
185+
.add-row-plus-btn.btn {
186+
line-height: 1;
187+
}
110188
}
111189

112190
.no-underline {
@@ -123,3 +201,7 @@ body.template-dynamic-view.can_edit {
123201
}
124202
}
125203
}
204+
205+
.dynamic-row .dropdown-menu {
206+
z-index: 1030; /* Ensure dropdown is above other elements */
207+
}

src/cs_dynamicpages/browser/static/reorder-rows.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,6 @@
44
(function () {
55
"use strict";
66

7-
// Store references to avoid re-querying the DOM
8-
let sortableInstance = null;
9-
const upClickHandlers = new Map();
10-
const downClickHandlers = new Map();
11-
let isInitialized = false;
12-
137
// Initialize when DOM is loaded
148
if (document.readyState === "loading") {
159
document.addEventListener("DOMContentLoaded", initRowReordering);
@@ -55,20 +49,20 @@
5549
if (element) {
5650
moveElementInDOM(element, delta);
5751
sendReorderRequest(element, delta);
52+
updateRowPositions(); // Recalculate plus button positions
5853
}
5954

6055
setTimeout(() => (button.disabled = false), 500);
6156
}
6257

6358
function initDragAndDropReordering() {
6459
if (typeof Sortable === "undefined") {
65-
console.log("SortableJS not loaded. Drag-and-drop reordering disabled.");
6660
return;
6761
}
6862

6963
// Find the container of the rows. We assume all draggable items share the same parent.
7064
const firstDraggableElement = document.querySelector(
71-
'[data-move-target="true"]'
65+
'.dynamic-row-wrapper'
7266
);
7367
if (!firstDraggableElement?.parentElement) {
7468
return;
@@ -85,18 +79,31 @@
8579
ghostClass: "sortable-ghost", // Class for the drop placeholder
8680
chosenClass: "sortable-chosen", // Class for the chosen item
8781
dragClass: "sortable-drag", // Class for the dragging item
82+
draggable: ".dynamic-row-wrapper", // Only allow dragging the wrappers
8883

8984
// Element is dropped
9085
onEnd: (evt) => {
9186
const { oldIndex, newIndex, item } = evt;
9287
if (oldIndex !== newIndex) {
9388
const delta = newIndex - oldIndex;
9489
sendReorderRequest(item, delta);
90+
updateRowPositions(); // Recalculate plus button positions
9591
}
9692
},
9793
});
9894
}
9995

96+
/**
97+
* Recalculates all data-position attributes for add-row-plus-buttons
98+
* based on their current order in the DOM.
99+
*/
100+
function updateRowPositions() {
101+
const plusButtons = document.querySelectorAll(".add-row-plus-btn");
102+
plusButtons.forEach((btn, index) => {
103+
btn.setAttribute("data-position", index);
104+
});
105+
}
106+
100107
function moveElementInDOM(element, delta) {
101108
const parent = element.parentNode;
102109
if (!parent) return;
467 Bytes
Binary file not shown.
459 Bytes
Binary file not shown.
7.99 KB
Binary file not shown.
459 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)