@rcsb/rcsb-documentation 0.0.1 → 0.0.3

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/package.json CHANGED
@@ -1,23 +1,22 @@
1
1
  {
2
2
  "name": "@rcsb/rcsb-documentation",
3
3
  "private": false,
4
- "version": "0.0.1",
5
- "main": "src/index.js",
4
+ "version": "0.0.03",
5
+ "main": "build/bundle.js",
6
6
  "files": [
7
- "build"
7
+ "build",
8
+ "server/docsApi.js"
8
9
  ],
9
10
  "dependencies": {
11
+ "node-fetch": "^3.3.2",
10
12
  "react": "^18.3.1",
11
13
  "react-dom": "^18.3.1",
12
- "react-router-dom": "^6.25.1",
13
- "react-scripts": "^5.0.1"
14
+ "react-router-dom": "^6.25.1"
14
15
  },
15
16
  "scripts": {
16
- "start": "react-scripts start",
17
- "build": "react-scripts build",
18
- "prepublishOnly": "npm run build",
19
- "test": "react-scripts test",
20
- "eject": "react-scripts eject"
17
+ "start": "webpack serve --mode development",
18
+ "build": "webpack --mode production",
19
+ "test": "echo \"Error: no test specified\" && exit 1"
21
20
  },
22
21
  "eslintConfig": {
23
22
  "extends": [
@@ -38,10 +37,20 @@
38
37
  ]
39
38
  },
40
39
  "devDependencies": {
40
+ "@babel/core": "^7.25.2",
41
41
  "@babel/plugin-transform-modules-commonjs": "^7.24.7",
42
+ "@babel/plugin-transform-runtime": "^7.25.4",
43
+ "@babel/preset-env": "^7.25.4",
44
+ "@babel/preset-react": "^7.24.7",
45
+ "@babel/runtime": "^7.25.6",
46
+ "@babel/runtime-corejs3": "^7.25.6",
47
+ "babel-loader": "^9.1.3",
42
48
  "css-loader": "^7.1.2",
43
- "react-app-rewired": "^2.2.1",
49
+ "html-webpack-plugin": "^5.6.0",
44
50
  "style-loader": "^4.0.0",
45
- "webpack-cli": "^5.1.4"
51
+ "webpack": "^5.94.0",
52
+ "webpack-cli": "^5.1.4",
53
+ "webpack-dev-server": "^5.1.0",
54
+ "webpack-node-externals": "^3.0.0"
46
55
  }
47
56
  }
@@ -0,0 +1,299 @@
1
+ // Check if fetch is available; otherwise, use node-fetch
2
+ if (typeof fetch === 'undefined') {
3
+ global.fetch = require('node-fetch');
4
+ }
5
+
6
+ // Use native fetch in the browser environment
7
+ const CONTENT_URL = 'https://cs.rcsb.org/content';
8
+ const GROUP = 'group';
9
+ const ITEM = 'item';
10
+ const ROOT_ID = 'rcsb-documentation';
11
+
12
+ // Cached objects
13
+ let dbLastUpdated = null;
14
+ let initialized = false;
15
+
16
+ let index = [],
17
+ group_idMap = {},
18
+ groupNameMap = {},
19
+ item_idMap = {},
20
+ hrefMap = {},
21
+ itemMap = {};
22
+
23
+ // Fetch the latest 'lastUpdated' value for the top-level id
24
+ async function fetchLastUpdated(env) {
25
+ const url = `${CONTENT_URL}/${env}/by-top-id/${ROOT_ID}/last-updated`;
26
+ const response = await fetch(url, { method: 'GET' });
27
+ const data = await response.json();
28
+ return data.lastUpdated;
29
+ }
30
+
31
+ // Fetch all items for the top-level id
32
+ async function fetchIndex(env) {
33
+ const url = `${CONTENT_URL}/${env}/by-top-id/${ROOT_ID}`;
34
+ const response = await fetch(url, { method: 'GET' });
35
+ const data = await response.json();
36
+ if (data && data.length) {
37
+ setIndex(data);
38
+ }
39
+ return data;
40
+ }
41
+
42
+ // Fetch a specific item by its id
43
+ async function fetchItem(env, id) {
44
+ const url = `${CONTENT_URL}/${env}/${id}`;
45
+ const response = await fetch(url, { method: 'GET' });
46
+ const item = await response.json();
47
+ return item;
48
+ }
49
+
50
+ // Initialize the index or check if it needs to be updated
51
+ async function initializeIndex(req) {
52
+ const env = getEnv(req);
53
+ let loadIndex = false;
54
+
55
+ const lastUpdated = await fetchLastUpdated(env);
56
+
57
+ if (!initialized || dbLastUpdated === null || lastUpdated > dbLastUpdated) {
58
+ dbLastUpdated = lastUpdated;
59
+ loadIndex = true;
60
+ }
61
+
62
+ if (loadIndex) {
63
+ await fetchIndex(env);
64
+ }
65
+ }
66
+
67
+ // Process and return the requested item
68
+ async function processItem(req, res) {
69
+ const env = getEnv(req);
70
+ await initializeIndex(req);
71
+ getItem(req, res, env);
72
+ }
73
+
74
+ // Get environment from the request
75
+ function getEnv(req) {
76
+ return req.app.locals.instance === 'production' ? 'production' : 'staging';
77
+ }
78
+
79
+ // Fetch and return the requested item
80
+ async function getItem(req, res, env) {
81
+ const instance = req.app.locals.instance;
82
+ let loadItem = false;
83
+ let reqUrl = req.originalUrl;
84
+
85
+ if (reqUrl.substring(reqUrl.length - 1) === '/') {
86
+ reqUrl = reqUrl.substring(0, reqUrl.length - 1);
87
+ }
88
+
89
+ if (reqUrl === '/docs') {
90
+ const href = getFirstItemHref();
91
+ res.redirect(href);
92
+ } else if (group_idMap[reqUrl]) {
93
+ const href = getFirstItemHref(group_idMap[reqUrl]);
94
+ if (href) res.redirect(href);
95
+ else res.status(404).render('error', { statusCode: 404 });
96
+ } else {
97
+ const _id = item_idMap[reqUrl];
98
+ if (_id) {
99
+ if (!itemMap[_id]) {
100
+ loadItem = true;
101
+ } else {
102
+ const url = `${CONTENT_URL}/${env}/last-updated/${_id}`;
103
+ const response = await fetch(url, { method: 'GET' });
104
+ const data = await response.json();
105
+
106
+ if (data.lastUpdated > itemMap[_id].lastUpdated) loadItem = true;
107
+ }
108
+
109
+ if (loadItem) {
110
+ const item = await fetchItem(env, _id);
111
+ const imgPath = getImagePath(env, item, _id);
112
+ item.html = item.html.replace(/<IMG_PATH>/g, imgPath).replace(/https:\/\/www.rcsb.org/g, '');
113
+ item.lastUpdatedStr = new Date(item.lastUpdated).toLocaleDateString('en-US');
114
+ item.href = hrefMap[_id];
115
+ itemMap[_id] = item;
116
+
117
+ const menuPath = getMenuPath(item);
118
+ returnData(_id, instance, item, menuPath, res);
119
+ } else {
120
+ const item = itemMap[_id];
121
+ const menuPath = getMenuPath(item);
122
+ returnData(_id, instance, item, menuPath, res);
123
+ }
124
+ } else {
125
+ res.status(404).render('error', { statusCode: 404 });
126
+ }
127
+ }
128
+ }
129
+
130
+ // Get image path based on environment and item properties
131
+ function getImagePath(env, item, _id) {
132
+ if (env === 'production') {
133
+ return item.useEast
134
+ ? `https://cdn.rcsb.org/rcsb-pdb/content-east/${_id}/`
135
+ : `https://cdn.rcsb.org/rcsb-pdb/content/${_id}/`;
136
+ } else {
137
+ return `https://cms.rcsb.org/file-uploads/content/${_id}/`;
138
+ }
139
+ }
140
+
141
+ // Get the first item href under a given group or the first item in the index if group_id is undefined
142
+ function getFirstItemHref(group_id) {
143
+ if (!group_id) {
144
+ for (let i = 0; i < index.length; i++) {
145
+ if (index[i].type === ITEM) return index[i].href;
146
+ }
147
+ return null;
148
+ } else {
149
+ let found = false;
150
+ for (let i = 0; i < index.length; i++) {
151
+ const node = index[i];
152
+ if (node.type === GROUP && node._id === group_id) found = true;
153
+ if (found && node.type === ITEM) return node.href;
154
+ }
155
+ return null;
156
+ }
157
+ }
158
+
159
+ // Set the index for fetched data
160
+ function setIndex(nodes) {
161
+ index.length = 0;
162
+ const groups = nodes.filter(node => node.type === GROUP);
163
+ const groupMap = {};
164
+ const depth = -1;
165
+
166
+ let root;
167
+
168
+ groups.forEach(group => {
169
+ groupMap[group._id] = group;
170
+ });
171
+
172
+ nodes.forEach(node => {
173
+ const { id, parent_id, type } = node;
174
+ const group = groupMap[parent_id];
175
+
176
+ if (type === GROUP && id === ROOT_ID) root = node;
177
+
178
+ if (group) {
179
+ if (!group.nodes) group.nodes = [];
180
+ group.nodes.push(node);
181
+ }
182
+ });
183
+
184
+ root.path = '/docs/';
185
+ rootToIndex(root, depth);
186
+ initialized = true;
187
+ }
188
+
189
+ // Recursively convert root object to index array
190
+ function rootToIndex(group, depth) {
191
+ const { id, nodes, path } = group;
192
+
193
+ if (id !== ROOT_ID) {
194
+ group.numNodes = nodes ? nodes.length : 0;
195
+ const obj = getIndexObj(group, depth);
196
+ index.push(obj);
197
+ }
198
+
199
+ if (nodes) {
200
+ nodes.forEach(node => {
201
+ node.href = path + node.id;
202
+ if (node.type === GROUP) {
203
+ const { _id, href, name } = node;
204
+ node.path = href + '/';
205
+ group_idMap[href] = _id;
206
+ groupNameMap[_id] = name;
207
+ rootToIndex(node, depth + 1);
208
+ } else {
209
+ hrefMap[node._id] = node.href;
210
+ item_idMap[node.href] = node._id;
211
+ index.push(getIndexObj(node, depth));
212
+ }
213
+ });
214
+ }
215
+ }
216
+
217
+ // Convert node to index node
218
+ function getIndexObj(node, depth) {
219
+ const { _id, href, name, parent_id, title, type } = node;
220
+ return type === GROUP ? { _id, depth, name, parent_id, type } : { _id, depth, href, parent_id, title, type };
221
+ }
222
+
223
+ // Get menu path for the item
224
+ function getMenuPath(item) {
225
+ const { _ids } = item;
226
+ let menuPaths = [];
227
+ _ids.forEach(_id => {
228
+ if (groupNameMap[_id]) menuPaths.push(groupNameMap[_id]);
229
+ });
230
+ return menuPaths.join(' > ');
231
+ }
232
+
233
+ // Return data to be rendered in the template
234
+ function returnData(_id, instance, item, menuPath, res) {
235
+ const { description, title } = item;
236
+ const menuObj = getMenuObj(item);
237
+ const { groupMap, menu } = menuObj;
238
+ res.render('docs', { description, groupMap, instance, item, menu, menuPath, title });
239
+ }
240
+
241
+ // Get the menu object for rendering
242
+ function getMenuObj(item) {
243
+ const menu = JSON.parse(JSON.stringify(index));
244
+ const groupMap = {};
245
+ const groups = menu.filter(o => o.type === GROUP);
246
+
247
+ groups.forEach(group => {
248
+ group._ids = [];
249
+ groupMap[group._id] = group;
250
+ if (group.depth === 0) group.show = true;
251
+ });
252
+
253
+ item.show = true;
254
+ setParentGroupState(groupMap, item);
255
+
256
+ menu.forEach(o => {
257
+ const { _id, parent_id, type } = o;
258
+ const parentGroup = groupMap[parent_id];
259
+
260
+ if (parentGroup) {
261
+ o.show = parentGroup.open;
262
+ parentGroup._ids.push(_id);
263
+ }
264
+ if (type === ITEM && o._id === item._id) o.selected = true;
265
+ });
266
+
267
+ return { groupMap, menu };
268
+ }
269
+
270
+ // Set parent group state for rendering
271
+ function setParentGroupState(groupMap, o) {
272
+ if (o.show) {
273
+ const parent = groupMap[o.parent_id];
274
+ if (parent) {
275
+ parent.open = true;
276
+ parent.show = true;
277
+ setParentGroupState(groupMap, parent);
278
+ }
279
+ }
280
+ }
281
+
282
+ // Export all functions using CommonJS
283
+ module.exports = {
284
+ fetchLastUpdated,
285
+ fetchIndex,
286
+ fetchItem,
287
+ processItem,
288
+ getEnv,
289
+ initializeIndex,
290
+ getItem,
291
+ getFirstItemHref,
292
+ setIndex,
293
+ rootToIndex,
294
+ getIndexObj,
295
+ getMenuPath,
296
+ returnData,
297
+ getMenuObj,
298
+ setParentGroupState,
299
+ };
package/src/index.js DELETED
@@ -1,48 +0,0 @@
1
- import React from 'react';
2
- import ReactDOM from 'react-dom/client';
3
- import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
4
- import routes from './routes';
5
- import ErrorBoundary from './components/ErrorBoundary';
6
-
7
- const NotFound = () => (
8
- <div>
9
- Oops, looks like this page is not exist. Try going back to the homepage.
10
- </div>
11
- );
12
-
13
- const AppContent = () => {
14
- return (
15
- <main className="doc-container mt-5">
16
- <Routes>
17
- {routes.map((route, index) => (
18
- <Route
19
- key={index}
20
- path={route.path}
21
- element={route.element}
22
- />
23
- ))}
24
- <Route path="*" element={<NotFound />} />
25
- </Routes>
26
- </main>
27
- );
28
- };
29
-
30
- //Set the global variable for basename
31
- const basename =window.__DOCUMENTATION_BASEROUTE__ || '';
32
- const App = () => {
33
- return (
34
- <ErrorBoundary>
35
- <Router basename={basename}>
36
- <AppContent />
37
- </Router>
38
- </ErrorBoundary>
39
- );
40
- };
41
-
42
- const root = ReactDOM.createRoot(document.getElementById('root'));
43
- root.render(
44
- <React.StrictMode>
45
- <App basename={basename} />
46
- </React.StrictMode>
47
- );
48
-