@cocreate/elements 1.43.2 → 1.44.2

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.
@@ -9,33 +9,36 @@ jobs:
9
9
  about:
10
10
  runs-on: ubuntu-latest
11
11
  steps:
12
- - name: Checkout
13
- uses: actions/checkout@v3
14
- - name: Setup Node.js
15
- uses: actions/setup-node@v3
16
- with:
17
- node-version: 16
18
- - name: Jaid/action-sync-node-meta
19
- uses: jaid/action-sync-node-meta@v1.4.0
20
- with:
21
- direction: overwrite-github
22
- githubToken: "${{ secrets.GITHUB }}"
12
+ - name: Checkout repository
13
+ uses: actions/checkout@v7
14
+
15
+ - name: Sync package.json description to GitHub About
16
+ env:
17
+ GITHUB_TOKEN: ${{ secrets.GITHUB }}
18
+ run: |
19
+ DESC=$(jq -r '.description // empty' package.json)
20
+ if [ -n "$DESC" ]; then
21
+ echo "Updating repository description to: $DESC"
22
+ gh api -X PATCH repos/${{ github.repository }} -f description="$DESC"
23
+ else
24
+ echo "No description found in package.json."
25
+ fi
23
26
 
24
27
  release:
25
28
  runs-on: ubuntu-latest
26
29
  steps:
27
- - name: Checkout
28
- uses: actions/checkout@v4
30
+ - name: Checkout repository
31
+ uses: actions/checkout@v7
29
32
  with:
30
- fetch-depth: 0 # Required so semantic-release can trace git tags/history
33
+ fetch-depth: 0
34
+ token: ${{ secrets.GITHUB }} # Authenticates git CLI for tag/commit pushes
31
35
 
32
36
  - name: Setup Node.js
33
- uses: actions/setup-node@v4
37
+ uses: actions/setup-node@v7
34
38
  with:
35
- node-version: 22
39
+ node-version: '24'
36
40
 
37
41
  - name: Install Semantic Release & Plugins
38
- # Installs semantic-release and its plugins on the runner
39
42
  run: |
40
43
  npm install -g semantic-release \
41
44
  @semantic-release/changelog \
@@ -43,12 +46,11 @@ jobs:
43
46
  @semantic-release/github \
44
47
  @semantic-release/git
45
48
 
46
- - name: Run Semantic Release (Native)
49
+ - name: Run Semantic Release
47
50
  id: semantic
48
- # This will automatically pick up your export default config file in the repository root
49
51
  run: npx semantic-release
50
52
  env:
51
- GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
53
+ GITHUB_TOKEN: "${{ secrets.GITHUB }}"
52
54
  NPM_TOKEN: "${{ secrets.NPM_TOKEN }}"
53
55
 
54
56
  outputs:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/elements",
3
- "version": "1.43.2",
3
+ "version": "1.44.2",
4
4
  "description": "Fetch content into any html element by using html5 attributes to query data stored in CoCreate headless CMS. If data is changed it will instantly reflect in html tags as changes are broadcasted in realtime",
5
5
  "keywords": [
6
6
  "elements",
@@ -36,16 +36,16 @@
36
36
  },
37
37
  "main": "./src/index.js",
38
38
  "dependencies": {
39
- "@cocreate/actions": "^1.22.1",
40
- "@cocreate/crud-client": "^1.35.0",
41
- "@cocreate/element-prototype": "^1.33.0",
42
- "@cocreate/filter": "^1.34.0",
43
- "@cocreate/observer": "^1.20.0",
44
- "@cocreate/render": "^1.48.0",
45
- "@cocreate/utils": "^1.44.0"
39
+ "@cocreate/actions": "^1.23.0",
40
+ "@cocreate/crud-client": "^1.36.0",
41
+ "@cocreate/element-prototype": "^1.34.0",
42
+ "@cocreate/filter": "^1.35.0",
43
+ "@cocreate/observer": "^1.21.0",
44
+ "@cocreate/render": "^1.49.0",
45
+ "@cocreate/utils": "^1.50.0"
46
46
  },
47
47
  "devDependencies": {
48
- "@cocreate/webpack": "^1.6.0"
48
+ "@cocreate/webpack": "^1.7.0"
49
49
  },
50
50
  "allowScripts": {
51
51
  "@cocreate/actions@1.21.4": true,
@@ -62,7 +62,6 @@
62
62
  "@cocreate/render@1.46.1": true,
63
63
  "@cocreate/socket-client@1.40.5": true,
64
64
  "@cocreate/utils@1.42.2": true,
65
- "@cocreate/uuid@1.12.4": true,
66
65
  "@cocreate/webpack@1.4.3": true
67
66
  }
68
67
  }
@@ -0,0 +1,282 @@
1
+ import {
2
+ queryElements,
3
+ getAttributeNames,
4
+ checkValue
5
+ } from "@cocreate/utils";
6
+ import Actions from "@cocreate/actions";
7
+ import CRUD from "@cocreate/crud-client";
8
+
9
+ import { elements, keys, debounce } from "./mapRegistry.js";
10
+ import { getDataObject } from "./getDataHandler.js";
11
+ import { setData } from "./setDataHandler.js";
12
+ import { deriveSubscriptionKeys } from "./subscribeHandler.js";
13
+
14
+ /**
15
+ * Reads data for one or more elements based on their dataKey scope.
16
+ *
17
+ * @param {Element|Element[]} element - Element(s) to read data for
18
+ * @param {Object} [data] - CRUD payload override
19
+ * @param {Object} [dataKey] - Data key reference
20
+ */
21
+ export async function read(element, data, dataKey) {
22
+ if (!dataKey) dataKey = { string: elements.get(element) };
23
+ if (!data) {
24
+ let existingData = keys.get(dataKey.string);
25
+ if (existingData && existingData.dataKey && existingData.dataKey.object)
26
+ data = { ...existingData.dataKey.object };
27
+ }
28
+
29
+ if (!dataKey || !data.type) return;
30
+ if (!data.$filter && (!data[data.type] || !data[data.type].length)) return;
31
+
32
+ if (!(element instanceof HTMLCollection) && !Array.isArray(element))
33
+ element = [element];
34
+
35
+ let delay = debounce.get(dataKey.string);
36
+ if (!delay) debounce.set(dataKey.string, (delay = {}));
37
+
38
+ if (!delay.elements) delay.elements = new Map();
39
+
40
+ for (let el of element) {
41
+ const isRead = el.getAttribute("read");
42
+ if (
43
+ isRead !== "false" &&
44
+ !el.hasRead &&
45
+ (!data.$filter || (data.$filter && data.$filter.isFilter !== false))
46
+ ) {
47
+ el.hasRead = true;
48
+ delay.elements.set(el, true);
49
+ } else {
50
+ // console.log("read skipped", el);
51
+ }
52
+ }
53
+
54
+ if (!delay.elements.size) return;
55
+
56
+ clearTimeout(delay.timer);
57
+ delay.timer = setTimeout(function () {
58
+ if (!data.$filter && data.type === "object") {
59
+ if (!data.object) return;
60
+ else if (typeof data.object === "string")
61
+ data.object = { _id: data.object };
62
+ else if (Array.isArray(data.object)) {
63
+ if (data.object.length)
64
+ data.object = { _id: data.object[0]._id };
65
+ } else if (!data.object._id.match(/^[0-9a-fA-F]{24}$/)) return;
66
+ }
67
+
68
+ data.method = data.type + ".read";
69
+
70
+ CRUD.send(data).then((responseData) => {
71
+ debounce.delete(dataKey.string);
72
+ let els = Array.from(delay.elements.keys());
73
+ clearTimeout(delay.timer);
74
+ delete delay.elements;
75
+ delete delay.timer;
76
+
77
+ setData(els, responseData);
78
+ });
79
+ }, 500);
80
+ }
81
+
82
+ /**
83
+ * Persists modifications for a single element or a form container to the database.
84
+ * Derives and attaches subscriptionKeys (both document-specific and list-scoped)
85
+ * prior to dispatching via CRUD.send().
86
+ *
87
+ * @param {Element} element - Input element or FORM instance
88
+ * @param {Object} [action] - Action metadata object
89
+ * @returns {Promise<Object[]>}
90
+ */
91
+ export async function save(element, action) {
92
+ if (!element) return;
93
+ let data, value, form;
94
+ let upsert = element.getAttribute("upsert");
95
+ if (upsert && upsert !== "false") upsert = true;
96
+
97
+ if (element.tagName === "FORM") {
98
+ data = await element.getData();
99
+ } else {
100
+ let isSave = element.getAttribute("save");
101
+ if (isSave === "false") return;
102
+ if (action) {
103
+ form = element.closest("form");
104
+ if (form) {
105
+ return save(form);
106
+ }
107
+ }
108
+
109
+ let dataKey = elements.get(element);
110
+ data = { ...keys.get(dataKey).dataKey.object };
111
+
112
+ value = await element.getValue();
113
+ let key = element.getAttribute("key");
114
+
115
+ if (typeof data[data.type] === "string")
116
+ if (key == "{}")
117
+ data[data.type] = { _id: data[data.type], ...value };
118
+ else data[data.type] = { _id: data[data.type], [key]: value };
119
+ else if (typeof data[data.type] === "object")
120
+ if (key == "{}") data[data.type] = { ...data[data.type], ...value };
121
+ else data[data.type][key] = value;
122
+
123
+ if (/\.([0-9]*)/g.test(data.key)) {
124
+ let splice = element.getAttribute("splice");
125
+ let slice = element.getAttribute("slice");
126
+ let update = element.getAttribute("update");
127
+
128
+ if (splice || splice === "") {
129
+ data[data.type][key] = { $splice: value };
130
+ } else if (slice) {
131
+ data[data.type][key] = "$delete";
132
+ } else if (update) {
133
+ value = data.key.replace(/\[.*?\]/, "[" + value + "]");
134
+ data.updateKey[key] = value;
135
+ data[data.type][key] = { $update: value };
136
+ }
137
+ }
138
+
139
+ data = [data];
140
+ }
141
+
142
+ let responseDataList = [];
143
+ for (let i = 0; i < data.length; i++) {
144
+ if (data[i].type === "object") {
145
+ let hasId = false;
146
+
147
+ if (typeof data[i].object === "string") {
148
+ hasId = !!data[i]._id || !!data[i].object;
149
+ } else if (Array.isArray(data[i].object)) {
150
+ hasId = !!data[i].object[0]?._id;
151
+ } else if (typeof data[i].object === "object" && data[i].object !== null) {
152
+ hasId = !!data[i].object?._id;
153
+ }
154
+
155
+ if (!data[i].object || !hasId) {
156
+ data[i].method = "object.create";
157
+ } else {
158
+ data[i].method = "object.update";
159
+ if (upsert) data[i].upsert = true;
160
+ }
161
+ }
162
+
163
+ if (data[i].isUpsert) {
164
+ data[i].upsert = true;
165
+ delete data[i].isUpsert;
166
+ }
167
+
168
+ if (data[i].method && data[i].method.endsWith(".create")) {
169
+ element.setAttribute(data[i].type, "pending");
170
+ } else if (
171
+ data[i].method &&
172
+ data[i].method.endsWith(".update") &&
173
+ data[i].type == "object" &&
174
+ typeof value == "string" &&
175
+ window.CoCreate?.crdt
176
+ ) {
177
+ return window.CoCreate.crdt.replaceText({
178
+ array: data[i].array,
179
+ key: data[i].key,
180
+ object: data[i].object._id,
181
+ value
182
+ });
183
+ }
184
+
185
+ // Derive and attach subscriptionKeys array (contains specific document key and list key)
186
+ data[i].subscriptionKeys = deriveSubscriptionKeys(data[i]);
187
+
188
+ data[i] = await CRUD.send(data[i]);
189
+
190
+ responseDataList.push(data[i]);
191
+
192
+ if (
193
+ data[i] &&
194
+ (data[i].method.endsWith(".create") ||
195
+ (data[i].type !== "object" &&
196
+ data[i].method.endsWith(".update")))
197
+ ) {
198
+ setDataType(element, data[i], action);
199
+ } else if (data[i]) {
200
+ if (action) {
201
+ action.element.dispatchEvent(
202
+ new CustomEvent("saved", {
203
+ detail: data
204
+ })
205
+ );
206
+ } else {
207
+ document.dispatchEvent(
208
+ new CustomEvent("saved", {
209
+ detail: data
210
+ })
211
+ );
212
+ }
213
+ }
214
+ }
215
+
216
+ return responseDataList;
217
+ }
218
+
219
+ Actions.init([
220
+ {
221
+ name: "save",
222
+ endEvent: "saved",
223
+ callback: (action) => {
224
+ if (action.form || action) save(action.form, action);
225
+ }
226
+ },
227
+ {
228
+ name: "delete",
229
+ endEvent: "deleted",
230
+ callback: async (action) => {
231
+ let elements = [];
232
+ if (action.element.hasAttribute("delete-query")) {
233
+ elements = queryElements({
234
+ element: action.element,
235
+ prefix: "delete"
236
+ });
237
+ } else {
238
+ elements = [action.element];
239
+ }
240
+
241
+ for (let i = 0; i < elements.length; i++) {
242
+ const data = getDataObject(elements[i]);
243
+ if (!data) return;
244
+ data.method = data.type + ".delete";
245
+
246
+ if (elements[i].renderValue) {
247
+ let selected = elements[i].querySelectorAll(".selected");
248
+ data[data.type] = [];
249
+ for (let j = 0; j < selected.length; j++) {
250
+ let attribute = selected[j].getAttribute(data.type);
251
+ if (attribute) {
252
+ attribute = attribute.split(",");
253
+ for (let k = 0; k < attribute.length; k++) {
254
+ if (data.type === "object")
255
+ data[data.type].push({ _id: attribute[k] });
256
+ else {
257
+ data[data.type].push(attribute[k]);
258
+ }
259
+ }
260
+ }
261
+ }
262
+ } else if (
263
+ data.type === "object" &&
264
+ typeof data[data.type] === "string"
265
+ )
266
+ data[data.type] = { _id: data[data.type] };
267
+
268
+ // Attach subscriptionKeys before sending delete payload
269
+ data.subscriptionKeys = deriveSubscriptionKeys(data);
270
+
271
+ let response = await CRUD.send(data);
272
+ action.element.dispatchEvent(
273
+ new CustomEvent("deleted", {
274
+ detail: response
275
+ })
276
+ );
277
+ }
278
+ }
279
+ }
280
+ ]);
281
+
282
+ export default { read, save };
@@ -0,0 +1,155 @@
1
+ import { dotNotationToObject } from "@cocreate/utils";
2
+ import CRUD from "@cocreate/crud-client";
3
+ import Observer from "@cocreate/observer";
4
+ import { render, renderValue, renderedNodes } from "@cocreate/render";
5
+ import { elements, keys, debounce } from "./mapRegistry.js"
6
+
7
+ // TODO: has the potential to work on most of the crud elements specifically if the value is an object or an array
8
+ async function dndCrud(draggedEl, draggedFrom, droppedEl, droppedIn) {
9
+ let from = await dndCrudData(draggedEl, draggedFrom, "remove");
10
+ let to = await dndCrudData(droppedEl, droppedIn, "add");
11
+
12
+ if (from && to && !draggedFrom.isSameNode(droppedIn)) {
13
+ let element = getDataElements(from.data);
14
+ if (!element.length) return;
15
+
16
+ if (!element.includes(droppedIn)) {
17
+ if (from.keyPath.includes(".")) {
18
+ let test = { [from.keyPath]: undefined };
19
+
20
+ from.clonedParentData = dotNotationToObject(test, from.clonedParentData);
21
+ dndCrudSend(from.clonedParentData, "update");
22
+ } else {
23
+ const index = from.keyPath.match(/\[(\d+)\]/);
24
+ let removeData = from.clonedParentData[from.clonedParentData.type].splice(
25
+ index,
26
+ 1
27
+ )[0];
28
+ if (removeData) {
29
+ removeData.type = from.clonedParentData.type;
30
+ removeData[removeData.type] = removeData;
31
+ dndCrudSend(removeData, "delete");
32
+ }
33
+
34
+ if (from.clonedParentData[from.clonedParentData.type].length)
35
+ dndCrudSend(from.clonedParentData, "update");
36
+ }
37
+ }
38
+ }
39
+
40
+ if (to) dndCrudSend(to.clonedParentData, "update");
41
+ }
42
+
43
+ async function dndCrudData(element, parent, operator) {
44
+ if (!elements.has(parent)) return;
45
+ let data = await parent.getData();
46
+ let clonedParentData = getDataObject(parent);
47
+
48
+ let { dragData, sortName, sortDirection, keyPath, clones, index } = dndNewData(
49
+ element,
50
+ data
51
+ );
52
+ clonedParentData[clonedParentData.type] = [];
53
+
54
+ if (sortName) {
55
+ for (let i = 0; i < clones.length; i++) {
56
+ if (i > index) {
57
+ let previousData = data[data.type][index];
58
+ dragData[sortName] = operator === "add" ? i + 1 : i - 1;
59
+
60
+ clonedParentData[clonedParentData.type].push({ ...previousData, ...dragData });
61
+ }
62
+ }
63
+ } else {
64
+ clonedParentData[clonedParentData.type] = [{ ...data[data.type][index], ...dragData }];
65
+ }
66
+
67
+ return { data, clonedParentData, keyPath, clones, index };
68
+ }
69
+
70
+ function dndNewData(element, data) {
71
+ let dragData = {};
72
+ if (data.$filter.query) {
73
+ dndNewDataUpdate(dragData, data.$filter.query);
74
+ }
75
+
76
+ let sortName, sortDirection;
77
+ let sort = data.$filter.sort;
78
+ if (sort && sort.length) {
79
+ for (let i = sort.length - 1; i >= 0; i--) {
80
+ if (typeof data[sort.key] === "number") {
81
+ sortName = sort.key;
82
+ sortDirection = sort.direction;
83
+ break;
84
+ }
85
+ }
86
+ }
87
+
88
+ let keyPath, clonesMap, clones, index;
89
+
90
+ let renderedNode = renderedNodes.get(element);
91
+ if (renderedNode) {
92
+ keyPath = renderedNode.keyPath;
93
+ clonesMap = renderedNode.template.clones;
94
+ clones = Array.from(clonesMap.values());
95
+ index = clones.indexOf(element);
96
+ }
97
+
98
+ return { dragData, sortName, sortDirection, keyPath, clones, index };
99
+ }
100
+
101
+ function dndNewDataUpdate(dragData, query) {
102
+ for (let key of Object.keys(query)) {
103
+ if (key === "$and" || key === "$or" || key === "$nor") {
104
+ for (let i = 0; i < query[key].length; i++) {
105
+ dndNewDataUpdate(dragData, query[key][i]);
106
+ }
107
+ } else if (
108
+ typeof query[key] === "object" &&
109
+ !Array.isArray(query[key])
110
+ ) {
111
+ if (query[key].$eq) dragData[key] = query[key].$eq;
112
+ if (query[key].$ne) dragData[key] = query[key].$ne;
113
+ } else {
114
+ dragData[key] = query[key];
115
+ }
116
+ }
117
+ }
118
+
119
+ function dndCrudSend(data, crudType) {
120
+ if (CRUD) {
121
+ let action = data.type;
122
+ action = action.charAt(0).toUpperCase() + action.slice(1);
123
+ CRUD[crudType + action](data);
124
+ } else console.log("dnd reordered data set as crud is unavailable");
125
+ }
126
+
127
+ Observer.init({
128
+ name: "render",
129
+ types: ["addedNodes"],
130
+ selector: "[render-clone]",
131
+ callback: function (mutation) {
132
+ if (
133
+ mutation.parentElement &&
134
+ !mutation.parentElement.hasAttribute("dnd")
135
+ )
136
+ return;
137
+ let delayTimer = debounce.get(mutation);
138
+ clearTimeout(delayTimer);
139
+ debounce.delete(mutation.target);
140
+
141
+ let renderedNode = renderedNodes.get(mutation.target);
142
+ if (!renderedNode) return;
143
+
144
+ if (!mutation.movedFrom) return;
145
+ let draggedEl = mutation.target;
146
+ let draggedFrom = mutation.movedFrom.parentElement;
147
+ let droppedEl =
148
+ mutation.target.nextElementSibling ||
149
+ mutation.target.previousElementSibling;
150
+ let droppedIn = mutation.parentElement;
151
+
152
+ if (!draggedFrom || !droppedIn) return;
153
+ dndCrud(draggedEl, draggedFrom, droppedEl, droppedIn);
154
+ }
155
+ });