@cocreate/server-side-render 1.12.4 → 1.12.6
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.
- package/CHANGELOG.md +19 -0
- package/package.json +1 -1
- package/src/index.js +124 -109
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## [1.12.6](https://github.com/CoCreate-app/CoCreate-server-side-render/compare/v1.12.5...v1.12.6) (2025-10-11)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* ensure pathname ends with a trailing slash for correct URL construction in HTML rendering ([5122e24](https://github.com/CoCreate-app/CoCreate-server-side-render/commit/5122e2413a986ebe51cb4a48ab92387aad4b5727))
|
|
7
|
+
|
|
8
|
+
## [1.12.5](https://github.com/CoCreate-app/CoCreate-server-side-render/compare/v1.12.4...v1.12.5) (2025-10-08)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* add TODO comments for fetching src and activating language links in HTML rendering ([0429a37](https://github.com/CoCreate-app/CoCreate-server-side-render/commit/0429a37715c648c46989f351befb8c5f19f5e181))
|
|
14
|
+
* enhance src handling and language link tag creation in HTML rendering ([1dc2bb6](https://github.com/CoCreate-app/CoCreate-server-side-render/commit/1dc2bb6d9c4839d605b3ddd1cd4374f9b10bf3ba))
|
|
15
|
+
* ensure proper context binding and improve translation logic in CoCreateServerSideRender ([74e5ab2](https://github.com/CoCreate-app/CoCreate-server-side-render/commit/74e5ab22a68d1eec14dd6a2600f2a8363eefff16))
|
|
16
|
+
* improve src handling and add getRelativePath utility in HTML rendering ([c8b17f8](https://github.com/CoCreate-app/CoCreate-server-side-render/commit/c8b17f8b603808325393503c897bed94ff93e36f))
|
|
17
|
+
* refactor language link tag creation and improve DOM manipulation in HTML rendering ([b860e08](https://github.com/CoCreate-app/CoCreate-server-side-render/commit/b860e08be2c89ea60aac758d56e2599ed1aeec3a))
|
|
18
|
+
* update HTML method to accept organization and language parameters for improved rendering ([515336e](https://github.com/CoCreate-app/CoCreate-server-side-render/commit/515336ea7e7cc975a5e65f07d0f5166d7bc2d9e1))
|
|
19
|
+
|
|
1
20
|
## [1.12.4](https://github.com/CoCreate-app/CoCreate-server-side-render/compare/v1.12.3...v1.12.4) (2025-09-01)
|
|
2
21
|
|
|
3
22
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cocreate/server-side-render",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.6",
|
|
4
4
|
"description": "A simple server-side-render component in vanilla javascript. Easily configured using HTML5 data-attributes and/or JavaScript API.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"server-side-render",
|
package/src/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
const { parse } = require("node-html-parser");
|
|
2
|
-
const { checkValue } = require("@cocreate/utils");
|
|
2
|
+
const { checkValue, getRelativePath, ObjectId } = require("@cocreate/utils");
|
|
3
|
+
const path = require("path");
|
|
3
4
|
|
|
4
5
|
class CoCreateServerSideRender {
|
|
5
6
|
constructor(crud) {
|
|
6
7
|
this.crud = crud;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
async HTML(file) {
|
|
10
|
+
async HTML(file, organization, urlObject, langRegion, lang) {
|
|
10
11
|
const self = this;
|
|
11
12
|
let ignoreElement = {
|
|
12
13
|
INPUT: true,
|
|
@@ -19,6 +20,8 @@ class CoCreateServerSideRender {
|
|
|
19
20
|
|
|
20
21
|
let dep = [];
|
|
21
22
|
let dbCache = new Map();
|
|
23
|
+
let organization_id = file.organization_id;
|
|
24
|
+
const host = urlObject.hostname;
|
|
22
25
|
|
|
23
26
|
async function render(dom, lastKey) {
|
|
24
27
|
// Handle elements with [array][key][object]
|
|
@@ -58,6 +61,7 @@ class CoCreateServerSideRender {
|
|
|
58
61
|
} else {
|
|
59
62
|
data = await self.crud.send({
|
|
60
63
|
method: "object.read",
|
|
64
|
+
host,
|
|
61
65
|
array,
|
|
62
66
|
object: { _id },
|
|
63
67
|
organization_id
|
|
@@ -88,143 +92,154 @@ class CoCreateServerSideRender {
|
|
|
88
92
|
dep.pop();
|
|
89
93
|
}
|
|
90
94
|
|
|
95
|
+
// ToDo: Fetch and render src, update relativePath. must have similar functionality to @cocreate/elements/fetch-src
|
|
91
96
|
// Handle elements with [src]
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
97
|
+
for (let el of dom.querySelectorAll(
|
|
98
|
+
"[src]:not(script, img, iframe, audio, video, source, track, input, embed, frame)"
|
|
99
|
+
)) {
|
|
100
|
+
let src = el.getAttribute("src");
|
|
101
|
+
if (!src) continue;
|
|
102
|
+
|
|
103
|
+
let path =
|
|
104
|
+
el.getAttribute("path") || getRelativePath(file.path);
|
|
105
|
+
|
|
106
|
+
if (path) {
|
|
107
|
+
src = src.replaceAll(/\$relativePath\/?/g, path);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let pathname = file.path
|
|
111
|
+
if (!pathname.endsWith("/")) {
|
|
112
|
+
pathname += "/";
|
|
113
|
+
}
|
|
114
|
+
// Construct actual pathname using src and the original URL
|
|
115
|
+
pathname = new URL(src, `http://localhost${pathname}`)
|
|
116
|
+
.pathname;
|
|
117
|
+
|
|
118
|
+
if (pathname.endsWith("/")) {
|
|
119
|
+
pathname += "index.html";
|
|
120
|
+
}
|
|
121
|
+
let $filter = {
|
|
122
|
+
query: {
|
|
123
|
+
pathname: pathname
|
|
124
|
+
}
|
|
125
|
+
}; // Use filter to structure query
|
|
126
|
+
|
|
127
|
+
let data = await self.crud.send({
|
|
128
|
+
method: "object.read",
|
|
129
|
+
host,
|
|
130
|
+
array: "files",
|
|
131
|
+
object: "",
|
|
132
|
+
$filter,
|
|
133
|
+
organization_id
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (
|
|
137
|
+
data &&
|
|
138
|
+
data.object &&
|
|
139
|
+
data.object[0] &&
|
|
140
|
+
data.object[0].src
|
|
141
|
+
) {
|
|
142
|
+
let chunk = data.object[0].src;
|
|
143
|
+
|
|
144
|
+
// Replace $relativePath in the fetched chunk
|
|
145
|
+
let path =
|
|
146
|
+
el.getAttribute("path") || getRelativePath(file.path);
|
|
147
|
+
if (path) {
|
|
148
|
+
chunk = chunk.replaceAll(/\$relativePath\/?/g, path);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Replace ObjectId() with a new ObjectId
|
|
152
|
+
chunk = chunk.replaceAll("ObjectId()", () => {
|
|
153
|
+
// Generate a NEW ObjectId inside the function
|
|
154
|
+
return ObjectId().toString(); // Return its string representation
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Parse chunk into DOM before recursive rendering
|
|
158
|
+
let chunkDom = parse(chunk);
|
|
159
|
+
chunkDom = await render(chunkDom);
|
|
160
|
+
|
|
161
|
+
el.setAttribute("rendered", "");
|
|
162
|
+
el.innerHTML = "";
|
|
163
|
+
// Append all child nodes of chunkDom to el
|
|
164
|
+
for (const child of chunkDom.childNodes) {
|
|
165
|
+
el.appendChild(child);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
139
169
|
|
|
140
170
|
return dom;
|
|
141
171
|
}
|
|
142
172
|
|
|
143
173
|
let dom = parse(file.src);
|
|
144
174
|
dom = await render(dom, "root");
|
|
145
|
-
if (
|
|
146
|
-
dom = translate(dom, file);
|
|
147
|
-
|
|
175
|
+
if (langRegion || lang) {
|
|
176
|
+
dom = await this.translate(dom, file, langRegion, lang);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (organization.languages && organization.languages.length > 0) {
|
|
180
|
+
let langLinkTags = this.createLanguageLinkTags(
|
|
181
|
+
file,
|
|
182
|
+
organization,
|
|
183
|
+
urlObject
|
|
184
|
+
);
|
|
148
185
|
const head = dom.querySelector("head");
|
|
149
186
|
if (head && langLinkTags) {
|
|
150
|
-
const linksFragment = parse(
|
|
151
|
-
|
|
152
|
-
);
|
|
153
|
-
for (const link of linksFragment.childNodes) {
|
|
187
|
+
const linksFragment = parse(langLinkTags);
|
|
188
|
+
for (const link of linksFragment.querySelectorAll("link")) {
|
|
154
189
|
head.appendChild(link);
|
|
155
190
|
}
|
|
191
|
+
// Remove the fragment node from the DOM if it exists
|
|
192
|
+
if (linksFragment.parentNode) {
|
|
193
|
+
linksFragment.remove();
|
|
194
|
+
}
|
|
156
195
|
}
|
|
157
196
|
}
|
|
197
|
+
|
|
158
198
|
dep = [];
|
|
159
199
|
dbCache.clear();
|
|
160
200
|
return dom.toString();
|
|
161
201
|
}
|
|
162
202
|
|
|
163
|
-
createLanguageLinkTags(file) {
|
|
164
|
-
let
|
|
165
|
-
|
|
166
|
-
if (file.name !== "index.html") {
|
|
167
|
-
if (xDefault.endsWith("/")) {
|
|
168
|
-
xDefault += file.name;
|
|
169
|
-
} else {
|
|
170
|
-
xDefault += "/" + file.name;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
let generatedLinksString = `<link rel="alternate" hreflang="x-default" href="${xDefault}">\n`;
|
|
174
|
-
|
|
175
|
-
// Step 1: Create a lookup object that maps base language to its path.
|
|
176
|
-
// This is done once for efficiency.
|
|
177
|
-
const paths = {};
|
|
178
|
-
for (const p of file.pathname) {
|
|
179
|
-
const secondSlashIndex = p.indexOf("/", 1);
|
|
180
|
-
const langKey = p.substring(1, secondSlashIndex); // e.g., 'en', 'es', 'pt'
|
|
181
|
-
const restOfPath = p.substring(secondSlashIndex);
|
|
182
|
-
paths[langKey] = restOfPath;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Step 2: Iterate through all supported languages and build the HTML string.
|
|
186
|
-
for (const language of file.languages) {
|
|
187
|
-
// Use the base language to find the correct path in our map
|
|
188
|
-
const path = paths[language] || paths[language.split("-")[0]];
|
|
189
|
-
|
|
190
|
-
// If a valid path exists, construct the full link
|
|
191
|
-
if (path) {
|
|
192
|
-
// Construct the full href URL using the full language code from the array
|
|
193
|
-
const hrefUrl = `https://${file.urlObject.hostname}/${language}${path}`;
|
|
203
|
+
createLanguageLinkTags(file, organization, urlObject) {
|
|
204
|
+
let generatedLinksString = `<link rel="alternate" hreflang="x-default" href="https://${urlObject.hostname}${file.pathname}">\n`;
|
|
194
205
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
206
|
+
for (const language of organization.languages) {
|
|
207
|
+
let langPath = `/${language}${file.pathname}`;
|
|
208
|
+
const hrefUrl = `https://${urlObject.hostname}${langPath}`;
|
|
209
|
+
generatedLinksString += `<link rel="alternate" hreflang="${language}" href="${hrefUrl}">\n`;
|
|
198
210
|
}
|
|
199
211
|
return generatedLinksString;
|
|
200
212
|
}
|
|
201
213
|
|
|
202
|
-
async translate(dom, file) {
|
|
203
|
-
|
|
204
|
-
let lang = file.lang;
|
|
205
|
-
if (file.translations & (langRegion || lang)) {
|
|
214
|
+
async translate(dom, file, langRegion, lang) {
|
|
215
|
+
if (file.translations && (langRegion || lang)) {
|
|
206
216
|
for (let translation of file.translations) {
|
|
207
|
-
let
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
217
|
+
let elements = dom.querySelectorAll(translation.selector) || [];
|
|
218
|
+
for (let el of elements) {
|
|
219
|
+
if (!el) continue;
|
|
220
|
+
if (translation.innerHTML) {
|
|
221
|
+
let content =
|
|
222
|
+
translation.innerHTML[langRegion] ||
|
|
223
|
+
translation.innerHTML[lang];
|
|
224
|
+
if (content) {
|
|
225
|
+
el.innerHTML = content;
|
|
226
|
+
}
|
|
214
227
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
228
|
+
if (translation.attributes) {
|
|
229
|
+
for (let [key, languageObj] of Object.entries(
|
|
230
|
+
translation.attributes
|
|
231
|
+
)) {
|
|
232
|
+
let value =
|
|
233
|
+
languageObj[langRegion] || languageObj[lang];
|
|
234
|
+
if (value) {
|
|
235
|
+
el.setAttribute(key, value);
|
|
236
|
+
}
|
|
223
237
|
}
|
|
224
238
|
}
|
|
225
239
|
}
|
|
226
240
|
}
|
|
227
241
|
}
|
|
242
|
+
return dom;
|
|
228
243
|
}
|
|
229
244
|
}
|
|
230
245
|
|