@thoughtbot/superglue 0.53.3 → 0.54.0
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/.babelrc.js +15 -0
- package/.prettierignore +6 -0
- package/.prettierrc +5 -0
- package/.tool-versions +1 -0
- package/LICENSE +21 -0
- package/dist/action_creators.d.mts +25 -0
- package/dist/action_creators.mjs +17 -0
- package/dist/action_creators.mjs.map +1 -0
- package/dist/chunk-MNVGYKSD.mjs +779 -0
- package/dist/chunk-MNVGYKSD.mjs.map +1 -0
- package/dist/cjs/action_creators.cjs +597 -0
- package/dist/cjs/action_creators.cjs.map +1 -0
- package/dist/cjs/superglue.cjs +1278 -0
- package/dist/cjs/superglue.cjs.map +1 -0
- package/dist/index-DfWsUSqv.d.mts +246 -0
- package/dist/superglue.d.mts +65 -0
- package/dist/superglue.mjs +613 -0
- package/dist/superglue.mjs.map +1 -0
- package/package.json +60 -22
- package/tsconfig.json +15 -0
- package/tsup.config.ts +29 -0
- package/typedoc.json +30 -0
- package/README.md +0 -126
- package/action_creators/index.js +0 -160
- package/action_creators/requests.js +0 -236
- package/actions.js +0 -17
- package/components/Nav.js +0 -258
- package/config.js +0 -8
- package/index.js +0 -265
- package/middleware.js +0 -78
- package/reducers/index.js +0 -263
- package/utils/helpers.js +0 -38
- package/utils/immutability.js +0 -170
- package/utils/index.js +0 -59
- package/utils/react.js +0 -47
- package/utils/request.js +0 -142
- package/utils/ujs.js +0 -133
- package/utils/url.js +0 -93
- package/utils/window.js +0 -15
|
@@ -0,0 +1,1278 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// lib/index.tsx
|
|
31
|
+
var lib_exports = {};
|
|
32
|
+
__export(lib_exports, {
|
|
33
|
+
ApplicationBase: () => ApplicationBase,
|
|
34
|
+
BEFORE_FETCH: () => BEFORE_FETCH,
|
|
35
|
+
BEFORE_REMOTE: () => BEFORE_REMOTE,
|
|
36
|
+
BEFORE_VISIT: () => BEFORE_VISIT,
|
|
37
|
+
COPY_PAGE: () => COPY_PAGE,
|
|
38
|
+
GRAFTING_ERROR: () => GRAFTING_ERROR,
|
|
39
|
+
GRAFTING_SUCCESS: () => GRAFTING_SUCCESS,
|
|
40
|
+
HISTORY_CHANGE: () => HISTORY_CHANGE,
|
|
41
|
+
REMOVE_PAGE: () => REMOVE_PAGE,
|
|
42
|
+
SAVE_RESPONSE: () => SAVE_RESPONSE,
|
|
43
|
+
UPDATE_FRAGMENTS: () => UPDATE_FRAGMENTS,
|
|
44
|
+
fragmentMiddleware: () => fragmentMiddleware,
|
|
45
|
+
getIn: () => getIn,
|
|
46
|
+
urlToPageKey: () => urlToPageKey
|
|
47
|
+
});
|
|
48
|
+
module.exports = __toCommonJS(lib_exports);
|
|
49
|
+
var import_react2 = __toESM(require("react"));
|
|
50
|
+
var import_url_parse4 = __toESM(require("url-parse"));
|
|
51
|
+
|
|
52
|
+
// lib/utils/url.ts
|
|
53
|
+
var import_url_parse = __toESM(require("url-parse"));
|
|
54
|
+
function pathQuery(url) {
|
|
55
|
+
const { pathname, query } = new import_url_parse.default(url, {});
|
|
56
|
+
return pathname + query;
|
|
57
|
+
}
|
|
58
|
+
function pathQueryHash(url) {
|
|
59
|
+
const { pathname, query, hash } = new import_url_parse.default(url, {});
|
|
60
|
+
return pathname + query + hash;
|
|
61
|
+
}
|
|
62
|
+
function withFormatJson(url) {
|
|
63
|
+
const parsed = new import_url_parse.default(url, {}, true);
|
|
64
|
+
parsed.query["format"] = "json";
|
|
65
|
+
return parsed.toString();
|
|
66
|
+
}
|
|
67
|
+
function pathWithoutBZParams(url) {
|
|
68
|
+
const parsed = new import_url_parse.default(url, {}, true);
|
|
69
|
+
const query = parsed.query;
|
|
70
|
+
delete query["props_at"];
|
|
71
|
+
delete query["format"];
|
|
72
|
+
parsed.set("query", query);
|
|
73
|
+
return pathQueryHash(parsed.toString());
|
|
74
|
+
}
|
|
75
|
+
function urlToPageKey(url) {
|
|
76
|
+
const parsed = new import_url_parse.default(url, {}, true);
|
|
77
|
+
const query = parsed.query;
|
|
78
|
+
delete query["props_at"];
|
|
79
|
+
delete query["format"];
|
|
80
|
+
parsed.set("query", query);
|
|
81
|
+
return pathQuery(parsed.toString());
|
|
82
|
+
}
|
|
83
|
+
function withoutHash(url) {
|
|
84
|
+
const parsed = new import_url_parse.default(url, {}, true);
|
|
85
|
+
parsed.set("hash", "");
|
|
86
|
+
return parsed.toString();
|
|
87
|
+
}
|
|
88
|
+
function withoutBusters(url) {
|
|
89
|
+
const parsed = new import_url_parse.default(url, {}, true);
|
|
90
|
+
const query = parsed.query;
|
|
91
|
+
delete query["format"];
|
|
92
|
+
parsed.set("query", query);
|
|
93
|
+
return pathQuery(parsed.toString());
|
|
94
|
+
}
|
|
95
|
+
function formatForXHR(url) {
|
|
96
|
+
const formats = [withoutHash, withFormatJson];
|
|
97
|
+
return formats.reduce((memo, f) => f(memo), url);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// lib/utils/helpers.ts
|
|
101
|
+
function argsForHistory(path) {
|
|
102
|
+
const pageKey = urlToPageKey(path);
|
|
103
|
+
return [
|
|
104
|
+
path,
|
|
105
|
+
{
|
|
106
|
+
superglue: true,
|
|
107
|
+
pageKey,
|
|
108
|
+
posX: 0,
|
|
109
|
+
posY: 0
|
|
110
|
+
}
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// lib/utils/immutability.ts
|
|
115
|
+
var canLookAhead = /^[\da-zA-Z\-_]+=[\da-zA-Z\-_]+$/;
|
|
116
|
+
var KeyPathError = class extends Error {
|
|
117
|
+
constructor(message) {
|
|
118
|
+
super(message);
|
|
119
|
+
this.name = "KeyPathError";
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
function getIn(node, path) {
|
|
123
|
+
const keyPath = normalizeKeyPath(path);
|
|
124
|
+
let result = node;
|
|
125
|
+
let i;
|
|
126
|
+
for (i = 0; i < keyPath.length; i++) {
|
|
127
|
+
const key = keyPath[i];
|
|
128
|
+
if (typeof result === "object" && result !== null) {
|
|
129
|
+
if (!Array.isArray(result) && canLookAhead.test(key)) {
|
|
130
|
+
throw new KeyPathError(
|
|
131
|
+
`Expected to find an Array when using the key: ${key}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
result = atKey(result, key);
|
|
135
|
+
} else {
|
|
136
|
+
throw new KeyPathError(
|
|
137
|
+
`Expected to traverse an Array or Obj, got ${JSON.stringify(result)}`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (i === keyPath.length) {
|
|
142
|
+
return result;
|
|
143
|
+
} else {
|
|
144
|
+
return void 0;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function clone(node) {
|
|
148
|
+
return Array.isArray(node) ? [].slice.call(node) : { ...node };
|
|
149
|
+
}
|
|
150
|
+
function getKey(node, key) {
|
|
151
|
+
if (Array.isArray(node) && Number.isNaN(Number(key))) {
|
|
152
|
+
const key_parts = Array.from(key.split("="));
|
|
153
|
+
const attr = key_parts[0];
|
|
154
|
+
const id = key_parts[1];
|
|
155
|
+
if (!id || !attr) {
|
|
156
|
+
return key;
|
|
157
|
+
}
|
|
158
|
+
let i;
|
|
159
|
+
let child;
|
|
160
|
+
for (i = 0; i < node.length; i++) {
|
|
161
|
+
child = node[i];
|
|
162
|
+
if (typeof child === "object" && !Array.isArray(child) && child !== null) {
|
|
163
|
+
const val = child[attr];
|
|
164
|
+
if (val && val.toString() === id) {
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
throw new KeyPathError(`Could not look ahead ${key} at ${child}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (i === node.length) {
|
|
172
|
+
throw new KeyPathError(`Could not find ${key} while looking ahead`);
|
|
173
|
+
}
|
|
174
|
+
return i;
|
|
175
|
+
} else {
|
|
176
|
+
return key;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function atKey(node, key) {
|
|
180
|
+
const actualKey = getKey(node, key);
|
|
181
|
+
if (Array.isArray(node)) {
|
|
182
|
+
return node[actualKey];
|
|
183
|
+
} else {
|
|
184
|
+
return node[actualKey];
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function normalizeKeyPath(path) {
|
|
188
|
+
if (typeof path === "string") {
|
|
189
|
+
path = path.replace(/ /g, "");
|
|
190
|
+
if (path === "") {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
return path.split(".");
|
|
194
|
+
} else {
|
|
195
|
+
return path;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function setIn(object, path, value) {
|
|
199
|
+
const keypath = normalizeKeyPath(path);
|
|
200
|
+
const results = { 0: object };
|
|
201
|
+
const parents = { 0: object };
|
|
202
|
+
let i;
|
|
203
|
+
for (i = 0; i < keypath.length; i++) {
|
|
204
|
+
const parent = parents[i];
|
|
205
|
+
if (!(typeof parent === "object" && parent !== null)) {
|
|
206
|
+
throw new KeyPathError(
|
|
207
|
+
`Expected to traverse an Array or Obj, got ${JSON.stringify(parent)}`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
const child = atKey(parent, keypath[i]);
|
|
211
|
+
parents[i + 1] = child;
|
|
212
|
+
}
|
|
213
|
+
results[keypath.length] = value;
|
|
214
|
+
for (i = keypath.length - 1; i >= 0; i--) {
|
|
215
|
+
const target = clone(parents[i]);
|
|
216
|
+
results[i] = target;
|
|
217
|
+
const key = getKey(results[i], keypath[i]);
|
|
218
|
+
if (Array.isArray(target)) {
|
|
219
|
+
target[key] = results[i + 1];
|
|
220
|
+
} else {
|
|
221
|
+
target[key] = results[i + 1];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return results[0];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// lib/action_creators/index.ts
|
|
228
|
+
var import_url_parse2 = __toESM(require("url-parse"));
|
|
229
|
+
|
|
230
|
+
// lib/actions.ts
|
|
231
|
+
var actions_exports = {};
|
|
232
|
+
__export(actions_exports, {
|
|
233
|
+
BEFORE_FETCH: () => BEFORE_FETCH,
|
|
234
|
+
BEFORE_REMOTE: () => BEFORE_REMOTE,
|
|
235
|
+
BEFORE_VISIT: () => BEFORE_VISIT,
|
|
236
|
+
COPY_PAGE: () => COPY_PAGE,
|
|
237
|
+
GRAFTING_ERROR: () => GRAFTING_ERROR,
|
|
238
|
+
GRAFTING_SUCCESS: () => GRAFTING_SUCCESS,
|
|
239
|
+
HANDLE_GRAFT: () => HANDLE_GRAFT,
|
|
240
|
+
HISTORY_CHANGE: () => HISTORY_CHANGE,
|
|
241
|
+
REMOVE_PAGE: () => REMOVE_PAGE,
|
|
242
|
+
SAVE_RESPONSE: () => SAVE_RESPONSE,
|
|
243
|
+
SET_CSRF_TOKEN: () => SET_CSRF_TOKEN,
|
|
244
|
+
SUPERGLUE_ERROR: () => SUPERGLUE_ERROR,
|
|
245
|
+
UPDATE_FRAGMENTS: () => UPDATE_FRAGMENTS
|
|
246
|
+
});
|
|
247
|
+
var BEFORE_FETCH = "@@superglue/BEFORE_FETCH";
|
|
248
|
+
var BEFORE_VISIT = "@@superglue/BEFORE_VISIT";
|
|
249
|
+
var BEFORE_REMOTE = "@@superglue/BEFORE_REMOTE";
|
|
250
|
+
var SAVE_RESPONSE = "@@superglue/SAVE_RESPONSE";
|
|
251
|
+
var HANDLE_GRAFT = "@@superglue/HANDLE_GRAFT";
|
|
252
|
+
var SUPERGLUE_ERROR = "@@superglue/ERROR";
|
|
253
|
+
var GRAFTING_ERROR = "@@superglue/GRAFTING_ERROR";
|
|
254
|
+
var GRAFTING_SUCCESS = "@@superglue/GRAFTING_SUCCESS";
|
|
255
|
+
var HISTORY_CHANGE = "@@superglue/HISTORY_CHANGE";
|
|
256
|
+
var SET_CSRF_TOKEN = "@@superglue/SET_CSRF_TOKEN";
|
|
257
|
+
var REMOVE_PAGE = "@@superglue/REMOVE_PAGE";
|
|
258
|
+
var COPY_PAGE = "@@superglue/COPY_PAGE";
|
|
259
|
+
var UPDATE_FRAGMENTS = "@@superglue/UPDATE_FRAGMENTS";
|
|
260
|
+
|
|
261
|
+
// lib/action_creators/requests.ts
|
|
262
|
+
function beforeRemote(payload) {
|
|
263
|
+
return {
|
|
264
|
+
type: BEFORE_REMOTE,
|
|
265
|
+
payload
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function beforeFetch(payload) {
|
|
269
|
+
return {
|
|
270
|
+
type: BEFORE_FETCH,
|
|
271
|
+
payload
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function handleError(err) {
|
|
275
|
+
return {
|
|
276
|
+
type: SUPERGLUE_ERROR,
|
|
277
|
+
payload: {
|
|
278
|
+
message: err.message
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
function handleFetchErr(err, fetchArgs, dispatch) {
|
|
283
|
+
dispatch(handleError(err));
|
|
284
|
+
throw err;
|
|
285
|
+
}
|
|
286
|
+
function buildMeta(pageKey, page, state, rsp, fetchArgs) {
|
|
287
|
+
const { assets: prevAssets } = state;
|
|
288
|
+
const { assets: nextAssets } = page;
|
|
289
|
+
return {
|
|
290
|
+
pageKey,
|
|
291
|
+
page,
|
|
292
|
+
redirected: rsp.redirected,
|
|
293
|
+
rsp,
|
|
294
|
+
fetchArgs,
|
|
295
|
+
componentIdentifier: page.componentIdentifier,
|
|
296
|
+
needsRefresh: needsRefresh(prevAssets, nextAssets)
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
var remote = (path, {
|
|
300
|
+
method = "GET",
|
|
301
|
+
headers,
|
|
302
|
+
body,
|
|
303
|
+
pageKey: rawPageKey,
|
|
304
|
+
beforeSave = (prevPage, receivedPage) => receivedPage
|
|
305
|
+
} = {}) => {
|
|
306
|
+
path = withoutBusters(path);
|
|
307
|
+
rawPageKey = rawPageKey && urlToPageKey(rawPageKey);
|
|
308
|
+
return (dispatch, getState) => {
|
|
309
|
+
const fetchArgs = argsForFetch(getState, path, {
|
|
310
|
+
method,
|
|
311
|
+
headers,
|
|
312
|
+
body
|
|
313
|
+
});
|
|
314
|
+
if (rawPageKey === void 0) {
|
|
315
|
+
rawPageKey = getState().superglue.currentPageKey;
|
|
316
|
+
}
|
|
317
|
+
const pageKey = rawPageKey;
|
|
318
|
+
const currentPageKey = getState().superglue.currentPageKey;
|
|
319
|
+
dispatch(beforeRemote({ currentPageKey, fetchArgs }));
|
|
320
|
+
dispatch(beforeFetch({ fetchArgs }));
|
|
321
|
+
return fetch(...fetchArgs).then(parseResponse).then(({ rsp, json }) => {
|
|
322
|
+
const { superglue, pages = {} } = getState();
|
|
323
|
+
const meta = buildMeta(pageKey, json, superglue, rsp, fetchArgs);
|
|
324
|
+
const willReplaceCurrent = pageKey == currentPageKey;
|
|
325
|
+
const existingId = pages[currentPageKey]?.componentIdentifier;
|
|
326
|
+
const receivedId = json.componentIdentifier;
|
|
327
|
+
if (willReplaceCurrent && !!existingId && existingId != receivedId) {
|
|
328
|
+
console.warn(
|
|
329
|
+
`You're about replace an existing page located at pages["${currentPageKey}"]
|
|
330
|
+
that has the componentIdentifier "${existingId}" with the contents of a
|
|
331
|
+
received page that has a componentIdentifier of "${receivedId}".
|
|
332
|
+
|
|
333
|
+
This can happen if you're using data-sg-remote or remote but your response
|
|
334
|
+
redirected to a completely different page. Since remote requests do not
|
|
335
|
+
navigate or change the current page component, your current page component may
|
|
336
|
+
receive a shape that is unexpected and cause issues with rendering.
|
|
337
|
+
|
|
338
|
+
Consider using data-sg-visit, the visit function, or redirect_back.`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
const page = beforeSave(pages[pageKey], json);
|
|
342
|
+
return dispatch(saveAndProcessPage(pageKey, page)).then(() => meta);
|
|
343
|
+
}).catch((e) => handleFetchErr(e, fetchArgs, dispatch));
|
|
344
|
+
};
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// lib/action_creators/index.ts
|
|
348
|
+
function copyPage({
|
|
349
|
+
from,
|
|
350
|
+
to
|
|
351
|
+
}) {
|
|
352
|
+
return {
|
|
353
|
+
type: COPY_PAGE,
|
|
354
|
+
payload: {
|
|
355
|
+
from,
|
|
356
|
+
to
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function saveResponse({
|
|
361
|
+
pageKey,
|
|
362
|
+
page
|
|
363
|
+
}) {
|
|
364
|
+
pageKey = urlToPageKey(pageKey);
|
|
365
|
+
return {
|
|
366
|
+
type: SAVE_RESPONSE,
|
|
367
|
+
payload: {
|
|
368
|
+
pageKey,
|
|
369
|
+
page
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
function handleGraft({
|
|
374
|
+
pageKey,
|
|
375
|
+
page
|
|
376
|
+
}) {
|
|
377
|
+
pageKey = urlToPageKey(pageKey);
|
|
378
|
+
return {
|
|
379
|
+
type: HANDLE_GRAFT,
|
|
380
|
+
payload: {
|
|
381
|
+
pageKey,
|
|
382
|
+
page
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function fetchDeferments(pageKey, defers = []) {
|
|
387
|
+
pageKey = urlToPageKey(pageKey);
|
|
388
|
+
return (dispatch) => {
|
|
389
|
+
const fetches = defers.filter(({ type }) => type === "auto").map(function({
|
|
390
|
+
url,
|
|
391
|
+
successAction = GRAFTING_SUCCESS,
|
|
392
|
+
failAction = GRAFTING_ERROR
|
|
393
|
+
}) {
|
|
394
|
+
const parsedUrl = new import_url_parse2.default(url, true);
|
|
395
|
+
const keyPath = parsedUrl.query.props_at;
|
|
396
|
+
return dispatch(remote(url, { pageKey })).then(() => {
|
|
397
|
+
dispatch({
|
|
398
|
+
type: successAction,
|
|
399
|
+
payload: {
|
|
400
|
+
pageKey,
|
|
401
|
+
keyPath
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}).catch((err) => {
|
|
405
|
+
dispatch({
|
|
406
|
+
type: failAction,
|
|
407
|
+
payload: {
|
|
408
|
+
url,
|
|
409
|
+
err,
|
|
410
|
+
pageKey,
|
|
411
|
+
keyPath
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
return Promise.all(fetches);
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function updateFragmentsUsing(page) {
|
|
420
|
+
const changedFragments = {};
|
|
421
|
+
page.fragments.forEach((fragment) => {
|
|
422
|
+
const { type, path } = fragment;
|
|
423
|
+
changedFragments[type] = getIn(page, path);
|
|
424
|
+
});
|
|
425
|
+
return {
|
|
426
|
+
type: UPDATE_FRAGMENTS,
|
|
427
|
+
payload: { changedFragments }
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
function saveAndProcessPage(pageKey, page) {
|
|
431
|
+
return (dispatch, getState) => {
|
|
432
|
+
pageKey = urlToPageKey(pageKey);
|
|
433
|
+
const { defers = [] } = page;
|
|
434
|
+
if ("action" in page) {
|
|
435
|
+
dispatch(handleGraft({ pageKey, page }));
|
|
436
|
+
} else {
|
|
437
|
+
dispatch(saveResponse({ pageKey, page }));
|
|
438
|
+
}
|
|
439
|
+
const hasFetch = typeof fetch != "undefined";
|
|
440
|
+
if (hasFetch) {
|
|
441
|
+
return dispatch(fetchDeferments(pageKey, defers)).then(() => {
|
|
442
|
+
if (page.fragments.length > 0) {
|
|
443
|
+
const finishedPage = getState().pages[pageKey];
|
|
444
|
+
dispatch(updateFragmentsUsing(finishedPage));
|
|
445
|
+
return Promise.resolve();
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
} else {
|
|
449
|
+
return Promise.resolve();
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// lib/utils/react.ts
|
|
455
|
+
function mapStateToProps(state, ownProps) {
|
|
456
|
+
let pageKey = ownProps.pageKey;
|
|
457
|
+
const params = ownProps;
|
|
458
|
+
const csrfToken = state.superglue.csrfToken;
|
|
459
|
+
pageKey = urlToPageKey(pageKey);
|
|
460
|
+
const { data, fragments } = state.pages[pageKey] || {
|
|
461
|
+
data: {},
|
|
462
|
+
fragments: []
|
|
463
|
+
};
|
|
464
|
+
return { ...data, ...params, pageKey, csrfToken, fragments };
|
|
465
|
+
}
|
|
466
|
+
var mapDispatchToProps = {
|
|
467
|
+
saveAndProcessPage,
|
|
468
|
+
copyPage
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
// lib/utils/request.ts
|
|
472
|
+
var import_url_parse3 = __toESM(require("url-parse"));
|
|
473
|
+
|
|
474
|
+
// lib/config.ts
|
|
475
|
+
var config = {
|
|
476
|
+
baseUrl: "",
|
|
477
|
+
maxPages: 20
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// lib/utils/request.ts
|
|
481
|
+
function isValidResponse(xhr) {
|
|
482
|
+
return isValidContent(xhr) && !downloadingFile(xhr);
|
|
483
|
+
}
|
|
484
|
+
function isValidContent(rsp) {
|
|
485
|
+
const contentType = rsp.headers.get("content-type");
|
|
486
|
+
const jsContent = /^(?:application\/json)(?:;|$)/;
|
|
487
|
+
return !!(contentType && contentType.match(jsContent));
|
|
488
|
+
}
|
|
489
|
+
function downloadingFile(xhr) {
|
|
490
|
+
const disposition = xhr.headers.get("content-disposition");
|
|
491
|
+
return !!(disposition && disposition.match(/^attachment/) !== null);
|
|
492
|
+
}
|
|
493
|
+
var SuperglueResponseError = class extends Error {
|
|
494
|
+
constructor(message) {
|
|
495
|
+
super(message);
|
|
496
|
+
this.name = "SuperglueResponseError";
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
function validateResponse(args) {
|
|
500
|
+
const { rsp } = args;
|
|
501
|
+
if (isValidResponse(rsp)) {
|
|
502
|
+
return args;
|
|
503
|
+
} else {
|
|
504
|
+
const error = new SuperglueResponseError("Invalid Superglue Response");
|
|
505
|
+
error.response = rsp;
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function handleServerErrors(args) {
|
|
510
|
+
const { rsp } = args;
|
|
511
|
+
if (!rsp.ok) {
|
|
512
|
+
if (rsp.status === 406) {
|
|
513
|
+
console.error(
|
|
514
|
+
"Superglue encountered a 406 Not Acceptable response. This can happen if you used respond_to and didn't specify format.json in the block. Try adding it to your respond_to. For example:\n\nrespond_to do |format|\n format.html\n format.json\n format.csv\nend"
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
const error = new SuperglueResponseError(rsp.statusText);
|
|
518
|
+
error.response = rsp;
|
|
519
|
+
throw error;
|
|
520
|
+
}
|
|
521
|
+
return args;
|
|
522
|
+
}
|
|
523
|
+
function argsForFetch(getState, pathQuery2, { method = "GET", headers = {}, body = "", signal } = {}) {
|
|
524
|
+
method = method.toUpperCase();
|
|
525
|
+
const currentState = getState().superglue;
|
|
526
|
+
const nextHeaders = { ...headers };
|
|
527
|
+
nextHeaders["x-requested-with"] = "XMLHttpRequest";
|
|
528
|
+
nextHeaders["accept"] = "application/json";
|
|
529
|
+
nextHeaders["x-superglue-request"] = "true";
|
|
530
|
+
if (method != "GET" && method != "HEAD") {
|
|
531
|
+
nextHeaders["content-type"] = "application/json";
|
|
532
|
+
}
|
|
533
|
+
if (body instanceof FormData) {
|
|
534
|
+
delete nextHeaders["content-type"];
|
|
535
|
+
}
|
|
536
|
+
if (currentState.csrfToken) {
|
|
537
|
+
nextHeaders["x-csrf-token"] = currentState.csrfToken;
|
|
538
|
+
}
|
|
539
|
+
const fetchPath = new import_url_parse3.default(
|
|
540
|
+
formatForXHR(pathQuery2),
|
|
541
|
+
config.baseUrl || {},
|
|
542
|
+
true
|
|
543
|
+
);
|
|
544
|
+
const credentials = "same-origin";
|
|
545
|
+
if (!(method == "GET" || method == "HEAD")) {
|
|
546
|
+
nextHeaders["x-http-method-override"] = method;
|
|
547
|
+
method = "POST";
|
|
548
|
+
}
|
|
549
|
+
const options = {
|
|
550
|
+
method,
|
|
551
|
+
headers: nextHeaders,
|
|
552
|
+
body,
|
|
553
|
+
credentials,
|
|
554
|
+
signal
|
|
555
|
+
};
|
|
556
|
+
if (currentState.currentPageKey) {
|
|
557
|
+
const referrer = new import_url_parse3.default(
|
|
558
|
+
currentState.currentPageKey,
|
|
559
|
+
config.baseUrl || {},
|
|
560
|
+
false
|
|
561
|
+
).href;
|
|
562
|
+
options.referrer = referrer;
|
|
563
|
+
}
|
|
564
|
+
if (method == "GET" || method == "HEAD") {
|
|
565
|
+
if (options.body instanceof FormData) {
|
|
566
|
+
const allData = new URLSearchParams(
|
|
567
|
+
options.body
|
|
568
|
+
);
|
|
569
|
+
const nextQuery = { ...fetchPath.query, ...Object.fromEntries(allData) };
|
|
570
|
+
fetchPath.set("query", nextQuery);
|
|
571
|
+
}
|
|
572
|
+
delete options.body;
|
|
573
|
+
}
|
|
574
|
+
return [fetchPath.toString(), options];
|
|
575
|
+
}
|
|
576
|
+
function extractJSON(rsp) {
|
|
577
|
+
return rsp.json().then((json) => {
|
|
578
|
+
return { rsp, json };
|
|
579
|
+
}).catch((e) => {
|
|
580
|
+
e.response = rsp;
|
|
581
|
+
throw e;
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
function parseResponse(prm) {
|
|
585
|
+
return Promise.resolve(prm).then(extractJSON).then(handleServerErrors).then(validateResponse);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// lib/utils/ujs.ts
|
|
589
|
+
var HandlerBuilder = class {
|
|
590
|
+
constructor({
|
|
591
|
+
ujsAttributePrefix,
|
|
592
|
+
visit,
|
|
593
|
+
remote: remote2
|
|
594
|
+
}) {
|
|
595
|
+
this.attributePrefix = ujsAttributePrefix;
|
|
596
|
+
this.isUJS = this.isUJS.bind(this);
|
|
597
|
+
this.handleSubmit = this.handleSubmit.bind(this);
|
|
598
|
+
this.handleClick = this.handleClick.bind(this);
|
|
599
|
+
this.visit = visit;
|
|
600
|
+
this.remote = remote2;
|
|
601
|
+
this.visitOrRemote = this.visitOrRemote.bind(this);
|
|
602
|
+
}
|
|
603
|
+
retrieveLink(target) {
|
|
604
|
+
const link = target.closest("a");
|
|
605
|
+
if (link && link.href.length !== 0) {
|
|
606
|
+
return link;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
isNonStandardClick(event) {
|
|
610
|
+
return event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
|
|
611
|
+
}
|
|
612
|
+
isUJS(node) {
|
|
613
|
+
const hasVisit = !!node.getAttribute(this.attributePrefix + "-visit");
|
|
614
|
+
const hasRemote = !!node.getAttribute(this.attributePrefix + "-remote");
|
|
615
|
+
return hasVisit || hasRemote;
|
|
616
|
+
}
|
|
617
|
+
handleSubmit(event) {
|
|
618
|
+
const form = event.target;
|
|
619
|
+
if (!(form instanceof HTMLFormElement)) {
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
if (!this.isUJS(form)) {
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
event.preventDefault();
|
|
626
|
+
let url = form.getAttribute("action");
|
|
627
|
+
if (!url) {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
const method = (form.getAttribute("method") || "POST").toUpperCase();
|
|
631
|
+
url = withoutBusters(url);
|
|
632
|
+
this.visitOrRemote(form, url, {
|
|
633
|
+
method,
|
|
634
|
+
body: new FormData(form)
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
handleClick(event) {
|
|
638
|
+
if (!(event.target instanceof Element)) {
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
const link = this.retrieveLink(event.target);
|
|
642
|
+
const isNonStandard = this.isNonStandardClick(event);
|
|
643
|
+
if (!link || isNonStandard || !this.isUJS(link)) {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
event.preventDefault();
|
|
647
|
+
let url = link.getAttribute("href");
|
|
648
|
+
if (!url) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
url = withoutBusters(url);
|
|
652
|
+
this.visitOrRemote(link, url, { method: "GET" });
|
|
653
|
+
}
|
|
654
|
+
visitOrRemote(linkOrForm, url, opts) {
|
|
655
|
+
if (linkOrForm.getAttribute(this.attributePrefix + "-visit")) {
|
|
656
|
+
const nextOpts = { ...opts };
|
|
657
|
+
const placeholderKey = linkOrForm.getAttribute(
|
|
658
|
+
this.attributePrefix + "-placeholder"
|
|
659
|
+
);
|
|
660
|
+
if (placeholderKey) {
|
|
661
|
+
nextOpts.placeholderKey = urlToPageKey(placeholderKey);
|
|
662
|
+
}
|
|
663
|
+
return this.visit(url, { ...nextOpts });
|
|
664
|
+
}
|
|
665
|
+
if (linkOrForm.getAttribute(this.attributePrefix + "-remote")) {
|
|
666
|
+
return this.remote(url, opts);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
handlers() {
|
|
670
|
+
return {
|
|
671
|
+
onClick: this.handleClick,
|
|
672
|
+
onSubmit: this.handleSubmit
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
var ujsHandlers = ({
|
|
677
|
+
ujsAttributePrefix,
|
|
678
|
+
visit,
|
|
679
|
+
remote: remote2
|
|
680
|
+
}) => {
|
|
681
|
+
const builder = new HandlerBuilder({
|
|
682
|
+
visit,
|
|
683
|
+
remote: remote2,
|
|
684
|
+
ujsAttributePrefix
|
|
685
|
+
});
|
|
686
|
+
return builder.handlers();
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
// lib/utils/window.ts
|
|
690
|
+
function needsRefresh(prevAssets, newAssets) {
|
|
691
|
+
if (prevAssets && newAssets) {
|
|
692
|
+
const hasNewAssets = !newAssets.every((asset) => prevAssets.includes(asset));
|
|
693
|
+
return hasNewAssets;
|
|
694
|
+
} else {
|
|
695
|
+
return false;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// lib/reducers/index.ts
|
|
700
|
+
function addPlaceholdersToDeferredNodes(existingPage, page) {
|
|
701
|
+
const { defers = [] } = existingPage;
|
|
702
|
+
const prevDefers = defers.map(({ path }) => {
|
|
703
|
+
const node = getIn(existingPage, path);
|
|
704
|
+
const copy = JSON.stringify(node);
|
|
705
|
+
return [path, JSON.parse(copy)];
|
|
706
|
+
});
|
|
707
|
+
return prevDefers.reduce((memo, [path, node]) => {
|
|
708
|
+
return setIn(page, path, node);
|
|
709
|
+
}, page);
|
|
710
|
+
}
|
|
711
|
+
function constrainPagesSize(state) {
|
|
712
|
+
const { maxPages } = config;
|
|
713
|
+
const allPageKeys = Object.keys(state);
|
|
714
|
+
const cacheTimesRecentFirst = allPageKeys.map((key) => state[key].savedAt).sort((a, b) => b - a);
|
|
715
|
+
for (const key of Array.from(allPageKeys)) {
|
|
716
|
+
if (state[key].savedAt <= cacheTimesRecentFirst[maxPages - 1]) {
|
|
717
|
+
delete state[key];
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
function saveResponse2(state, pageKey, page) {
|
|
722
|
+
state = { ...state };
|
|
723
|
+
let nextPage = {
|
|
724
|
+
pageKey,
|
|
725
|
+
...page,
|
|
726
|
+
savedAt: Date.now()
|
|
727
|
+
};
|
|
728
|
+
const existingPage = state[pageKey];
|
|
729
|
+
if (existingPage) {
|
|
730
|
+
nextPage = addPlaceholdersToDeferredNodes(existingPage, nextPage);
|
|
731
|
+
}
|
|
732
|
+
constrainPagesSize(state);
|
|
733
|
+
state[pageKey] = nextPage;
|
|
734
|
+
return state;
|
|
735
|
+
}
|
|
736
|
+
function appendReceivedFragmentsOntoPage(state, pageKey, receivedFragments) {
|
|
737
|
+
if (!pageKey) {
|
|
738
|
+
return state;
|
|
739
|
+
}
|
|
740
|
+
if (receivedFragments.length === 0) {
|
|
741
|
+
return state;
|
|
742
|
+
}
|
|
743
|
+
const currentPage = state[pageKey];
|
|
744
|
+
const { fragments: prevFragments = [] } = currentPage;
|
|
745
|
+
const nextFragments = [...prevFragments];
|
|
746
|
+
const existingKeys = {};
|
|
747
|
+
prevFragments.forEach((frag) => existingKeys[frag.path] = true);
|
|
748
|
+
receivedFragments.forEach((frag) => {
|
|
749
|
+
if (!existingKeys[frag.path]) {
|
|
750
|
+
nextFragments.push(frag);
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
const nextPage = {
|
|
754
|
+
...currentPage,
|
|
755
|
+
fragments: nextFragments
|
|
756
|
+
};
|
|
757
|
+
const nextState = { ...state };
|
|
758
|
+
nextState[pageKey] = nextPage;
|
|
759
|
+
return nextState;
|
|
760
|
+
}
|
|
761
|
+
function graftNodeOntoPage(state, pageKey, node, pathToNode) {
|
|
762
|
+
if (!node) {
|
|
763
|
+
console.warn(
|
|
764
|
+
"There was no node returned in the response. Do you have the correct key path in your props_at?"
|
|
765
|
+
);
|
|
766
|
+
return state;
|
|
767
|
+
}
|
|
768
|
+
if (!pathToNode || !pageKey) {
|
|
769
|
+
return state;
|
|
770
|
+
}
|
|
771
|
+
const fullPathToNode = [pageKey, pathToNode].join(".");
|
|
772
|
+
return setIn(state, fullPathToNode, node);
|
|
773
|
+
}
|
|
774
|
+
function handleGraft2(state, pageKey, page) {
|
|
775
|
+
const currentPage = state[pageKey];
|
|
776
|
+
if (!currentPage) {
|
|
777
|
+
const error = new Error(
|
|
778
|
+
`Superglue was looking for ${pageKey} in your state, but could not find it in your mapping. Did you forget to pass in a valid pageKey to this.props.remote or this.props.visit?`
|
|
779
|
+
);
|
|
780
|
+
throw error;
|
|
781
|
+
}
|
|
782
|
+
const {
|
|
783
|
+
data: receivedNode,
|
|
784
|
+
path: pathToNode,
|
|
785
|
+
fragments: receivedFragments = []
|
|
786
|
+
} = page;
|
|
787
|
+
return [
|
|
788
|
+
(nextState) => graftNodeOntoPage(nextState, pageKey, receivedNode, pathToNode),
|
|
789
|
+
(nextState) => appendReceivedFragmentsOntoPage(nextState, pageKey, receivedFragments)
|
|
790
|
+
].reduce((memo, fn) => fn(memo), state);
|
|
791
|
+
}
|
|
792
|
+
function pageReducer(state = {}, action) {
|
|
793
|
+
switch (action.type) {
|
|
794
|
+
case SAVE_RESPONSE: {
|
|
795
|
+
const { pageKey, page } = action.payload;
|
|
796
|
+
return saveResponse2(state, pageKey, page);
|
|
797
|
+
}
|
|
798
|
+
case HANDLE_GRAFT: {
|
|
799
|
+
const { pageKey, page } = action.payload;
|
|
800
|
+
return handleGraft2(state, pageKey, page);
|
|
801
|
+
}
|
|
802
|
+
case UPDATE_FRAGMENTS: {
|
|
803
|
+
const { changedFragments } = action.payload;
|
|
804
|
+
let nextState = state;
|
|
805
|
+
Object.entries(state).forEach(([pageKey, page]) => {
|
|
806
|
+
page.fragments.forEach((fragment) => {
|
|
807
|
+
const { type, path } = fragment;
|
|
808
|
+
const changedNode = changedFragments[type];
|
|
809
|
+
const currentNode = getIn(nextState, `${pageKey}.${path}`);
|
|
810
|
+
if (type in changedFragments && changedNode !== currentNode) {
|
|
811
|
+
const nextNode = JSON.parse(JSON.stringify(changedNode));
|
|
812
|
+
nextState = setIn(nextState, `${pageKey}.${path}`, nextNode);
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
});
|
|
816
|
+
return nextState;
|
|
817
|
+
}
|
|
818
|
+
case COPY_PAGE: {
|
|
819
|
+
const nextState = { ...state };
|
|
820
|
+
const { from, to } = action.payload;
|
|
821
|
+
nextState[urlToPageKey(to)] = JSON.parse(JSON.stringify(nextState[from]));
|
|
822
|
+
return nextState;
|
|
823
|
+
}
|
|
824
|
+
case REMOVE_PAGE: {
|
|
825
|
+
const { pageKey } = action.payload;
|
|
826
|
+
const nextState = { ...state };
|
|
827
|
+
delete nextState[pageKey];
|
|
828
|
+
return nextState;
|
|
829
|
+
}
|
|
830
|
+
default:
|
|
831
|
+
return state;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
function superglueReducer(state = {}, action) {
|
|
835
|
+
switch (action.type) {
|
|
836
|
+
case HISTORY_CHANGE: {
|
|
837
|
+
const { pathname, search, hash } = action.payload;
|
|
838
|
+
const currentPageKey = urlToPageKey(pathname + search);
|
|
839
|
+
return {
|
|
840
|
+
...state,
|
|
841
|
+
currentPageKey,
|
|
842
|
+
pathname,
|
|
843
|
+
search,
|
|
844
|
+
hash
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
case SAVE_RESPONSE: {
|
|
848
|
+
const {
|
|
849
|
+
page: { csrfToken, assets }
|
|
850
|
+
} = action.payload;
|
|
851
|
+
return { ...state, csrfToken, assets };
|
|
852
|
+
}
|
|
853
|
+
case SET_CSRF_TOKEN: {
|
|
854
|
+
const { csrfToken } = action.payload;
|
|
855
|
+
return { ...state, csrfToken };
|
|
856
|
+
}
|
|
857
|
+
default:
|
|
858
|
+
return state;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
var rootReducer = {
|
|
862
|
+
superglue: superglueReducer,
|
|
863
|
+
pages: pageReducer
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
// lib/index.tsx
|
|
867
|
+
var import_react_redux = require("react-redux");
|
|
868
|
+
var import_history = require("history");
|
|
869
|
+
|
|
870
|
+
// lib/components/Nav.tsx
|
|
871
|
+
var import_react = __toESM(require("react"));
|
|
872
|
+
var Nav = class extends import_react.default.Component {
|
|
873
|
+
/**
|
|
874
|
+
* @ignore
|
|
875
|
+
*/
|
|
876
|
+
constructor(props) {
|
|
877
|
+
super(props);
|
|
878
|
+
const { history, initialPageKey } = this.props;
|
|
879
|
+
this.history = history;
|
|
880
|
+
this.navigateTo = this.navigateTo.bind(this);
|
|
881
|
+
this.scrollTo = this.scrollTo.bind(this);
|
|
882
|
+
this.onHistoryChange = this.onHistoryChange.bind(this);
|
|
883
|
+
this.state = {
|
|
884
|
+
pageKey: initialPageKey,
|
|
885
|
+
ownProps: {}
|
|
886
|
+
};
|
|
887
|
+
this.hasWindow = typeof window !== "undefined";
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* @ignore
|
|
891
|
+
*/
|
|
892
|
+
componentDidMount() {
|
|
893
|
+
this.unsubscribeHistory = this.history.listen(this.onHistoryChange);
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* @ignore
|
|
897
|
+
*/
|
|
898
|
+
componentWillUnmount() {
|
|
899
|
+
this.unsubscribeHistory();
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Passed to every page component. Manually navigate using pages that exists
|
|
903
|
+
* in the store and restores scroll position. This is what {@link Visit} in
|
|
904
|
+
* your `application_visit.js` ultimately calls.
|
|
905
|
+
*
|
|
906
|
+
* If there is an existing page in your store `navigateTo` will restore the props,
|
|
907
|
+
* render the correct component, and return `true`. Otherwise, it will return
|
|
908
|
+
* `false`. This is useful if you want to restore an existing page before making a
|
|
909
|
+
* call to `visit` or `remote`.
|
|
910
|
+
*
|
|
911
|
+
* @param path
|
|
912
|
+
* @param options when `none`, immediately returns `false`
|
|
913
|
+
* @returns `true` if the navigation was a success, `false` if the page was not found in the
|
|
914
|
+
* store.
|
|
915
|
+
*/
|
|
916
|
+
navigateTo(path, {
|
|
917
|
+
action,
|
|
918
|
+
ownProps
|
|
919
|
+
} = {
|
|
920
|
+
action: "push",
|
|
921
|
+
ownProps: {}
|
|
922
|
+
}) {
|
|
923
|
+
if (action === "none") {
|
|
924
|
+
return false;
|
|
925
|
+
}
|
|
926
|
+
path = pathWithoutBZParams(path);
|
|
927
|
+
const nextPageKey = urlToPageKey(path);
|
|
928
|
+
const { store } = this.props;
|
|
929
|
+
const hasPage = !!store.getState().pages[nextPageKey];
|
|
930
|
+
if (hasPage) {
|
|
931
|
+
const location = this.history.location;
|
|
932
|
+
const state = location.state;
|
|
933
|
+
const prevPageKey = state.pageKey;
|
|
934
|
+
const historyArgs = [
|
|
935
|
+
path,
|
|
936
|
+
{
|
|
937
|
+
pageKey: nextPageKey,
|
|
938
|
+
superglue: true,
|
|
939
|
+
posY: 0,
|
|
940
|
+
posX: 0
|
|
941
|
+
}
|
|
942
|
+
];
|
|
943
|
+
if (action === "push") {
|
|
944
|
+
if (this.hasWindow) {
|
|
945
|
+
this.history.replace(
|
|
946
|
+
{
|
|
947
|
+
pathname: location.pathname,
|
|
948
|
+
search: location.search,
|
|
949
|
+
hash: location.hash
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
...state,
|
|
953
|
+
posY: window.pageYOffset,
|
|
954
|
+
posX: window.pageXOffset
|
|
955
|
+
}
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
this.history.push(...historyArgs);
|
|
959
|
+
}
|
|
960
|
+
if (action === "replace") {
|
|
961
|
+
this.history.replace(...historyArgs);
|
|
962
|
+
}
|
|
963
|
+
this.setState({ pageKey: nextPageKey, ownProps });
|
|
964
|
+
this.scrollTo(0, 0);
|
|
965
|
+
if (action === "replace" && prevPageKey && prevPageKey !== nextPageKey) {
|
|
966
|
+
store.dispatch({
|
|
967
|
+
type: REMOVE_PAGE,
|
|
968
|
+
payload: {
|
|
969
|
+
pageKey: prevPageKey
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
return true;
|
|
974
|
+
} else {
|
|
975
|
+
console.warn(
|
|
976
|
+
`\`navigateTo\` was called , but could not find.
|
|
977
|
+
the pageKey in the store. This may happen when the wrong
|
|
978
|
+
content_location was set in your non-get controller action.
|
|
979
|
+
No navigation will take place`
|
|
980
|
+
);
|
|
981
|
+
return false;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* @ignore
|
|
986
|
+
*/
|
|
987
|
+
scrollTo(posX, posY) {
|
|
988
|
+
this.hasWindow && window.scrollTo(posX, posY);
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* @ignore
|
|
992
|
+
*/
|
|
993
|
+
onHistoryChange({ location, action }) {
|
|
994
|
+
const { store, visit } = this.props;
|
|
995
|
+
const { pathname, search, hash } = location;
|
|
996
|
+
const state = location.state;
|
|
997
|
+
if (state && "superglue" in state) {
|
|
998
|
+
store.dispatch({
|
|
999
|
+
type: HISTORY_CHANGE,
|
|
1000
|
+
payload: { pathname, search, hash }
|
|
1001
|
+
});
|
|
1002
|
+
if (action !== "POP") {
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
const { pageKey, posX, posY } = state;
|
|
1006
|
+
const containsKey = !!store.getState().pages[pageKey];
|
|
1007
|
+
if (containsKey) {
|
|
1008
|
+
const { restoreStrategy } = store.getState().pages[pageKey];
|
|
1009
|
+
switch (restoreStrategy) {
|
|
1010
|
+
case "fromCacheOnly":
|
|
1011
|
+
this.setState({ pageKey });
|
|
1012
|
+
this.scrollTo(posX, posY);
|
|
1013
|
+
break;
|
|
1014
|
+
case "fromCacheAndRevisitInBackground":
|
|
1015
|
+
this.setState({ pageKey });
|
|
1016
|
+
this.scrollTo(posX, posY);
|
|
1017
|
+
visit(pageKey, { revisit: true });
|
|
1018
|
+
break;
|
|
1019
|
+
case "revisitOnly":
|
|
1020
|
+
default:
|
|
1021
|
+
visit(pageKey, { revisit: true }).then((meta) => {
|
|
1022
|
+
if (meta === void 0) {
|
|
1023
|
+
console.warn(
|
|
1024
|
+
`scoll restoration was skipped. Your visit's then funtion
|
|
1025
|
+
should return the meta object it recieved if you want your
|
|
1026
|
+
application to restore the page's previous scroll.`
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
if (!!meta && meta.suggestedAction === "none") {
|
|
1030
|
+
this.setState({ pageKey });
|
|
1031
|
+
this.scrollTo(posX, posY);
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
} else {
|
|
1036
|
+
visit(pageKey, { revisit: true }).then((meta) => {
|
|
1037
|
+
if (meta === void 0) {
|
|
1038
|
+
console.warn(
|
|
1039
|
+
`scoll restoration was skipped. Your visit's then funtion
|
|
1040
|
+
should return the meta object it recieved if you want your
|
|
1041
|
+
application to restore the page's previous scroll.`
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
if (!!meta && meta.suggestedAction === "none") {
|
|
1045
|
+
this.setState({ pageKey });
|
|
1046
|
+
this.scrollTo(posX, posY);
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* @ignore
|
|
1054
|
+
*/
|
|
1055
|
+
notFound(identifier) {
|
|
1056
|
+
let reminder = "";
|
|
1057
|
+
if (!identifier) {
|
|
1058
|
+
reminder = "Did you forget to add `json.componentIdentifier` in your application.json.props layout?";
|
|
1059
|
+
}
|
|
1060
|
+
const error = new Error(
|
|
1061
|
+
`Superglue Nav component was looking for ${identifier} but could not find it in your mapping. ${reminder}`
|
|
1062
|
+
);
|
|
1063
|
+
throw error;
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* @ignore
|
|
1067
|
+
*/
|
|
1068
|
+
render() {
|
|
1069
|
+
const { store, visit, remote: remote2 } = this.props;
|
|
1070
|
+
const { pageKey, ownProps } = this.state;
|
|
1071
|
+
const { componentIdentifier } = store.getState().pages[pageKey];
|
|
1072
|
+
const Component = this.props.mapping[componentIdentifier];
|
|
1073
|
+
if (Component) {
|
|
1074
|
+
return /* @__PURE__ */ import_react.default.createElement(
|
|
1075
|
+
Component,
|
|
1076
|
+
{
|
|
1077
|
+
pageKey,
|
|
1078
|
+
navigateTo: this.navigateTo,
|
|
1079
|
+
visit,
|
|
1080
|
+
remote: remote2,
|
|
1081
|
+
...ownProps
|
|
1082
|
+
}
|
|
1083
|
+
);
|
|
1084
|
+
} else {
|
|
1085
|
+
this.notFound(componentIdentifier);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
var Nav_default = Nav;
|
|
1090
|
+
|
|
1091
|
+
// lib/middleware.ts
|
|
1092
|
+
var actionValues = Object.values(actions_exports).map((action) => action.toString());
|
|
1093
|
+
var fragmentMiddleware = (store) => (next) => (action) => {
|
|
1094
|
+
const prevState = store.getState();
|
|
1095
|
+
const nextAction = next(action);
|
|
1096
|
+
const nextState = store.getState();
|
|
1097
|
+
if (!(action instanceof Object && "type" in action && typeof action.type === "string")) {
|
|
1098
|
+
return nextAction;
|
|
1099
|
+
}
|
|
1100
|
+
const type = action.type;
|
|
1101
|
+
if (actionValues.includes(type)) {
|
|
1102
|
+
return nextAction;
|
|
1103
|
+
}
|
|
1104
|
+
if (prevState.pages === nextState.pages) {
|
|
1105
|
+
return nextAction;
|
|
1106
|
+
}
|
|
1107
|
+
const changedFragments = {};
|
|
1108
|
+
const changedKeys = Object.keys(nextState.pages).filter((key) => {
|
|
1109
|
+
return prevState.pages[key] !== nextState.pages[key];
|
|
1110
|
+
});
|
|
1111
|
+
if (changedKeys.length === 0) {
|
|
1112
|
+
return nextAction;
|
|
1113
|
+
}
|
|
1114
|
+
changedKeys.forEach((key) => {
|
|
1115
|
+
nextState.pages[key].fragments.forEach((fragment) => {
|
|
1116
|
+
const { type: type2, path } = fragment;
|
|
1117
|
+
const nextPage = nextState.pages[key];
|
|
1118
|
+
const prevPage = prevState.pages[key];
|
|
1119
|
+
let nextFragment, prevFragment;
|
|
1120
|
+
try {
|
|
1121
|
+
prevFragment = getIn(prevPage, path);
|
|
1122
|
+
nextFragment = getIn(nextPage, path);
|
|
1123
|
+
} catch (err) {
|
|
1124
|
+
if (err instanceof KeyPathError) {
|
|
1125
|
+
console.warn(err.message);
|
|
1126
|
+
} else {
|
|
1127
|
+
throw err;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
if (nextFragment !== void 0 && prevFragment !== void 0 && nextFragment !== prevFragment && nextFragment) {
|
|
1131
|
+
changedFragments[type2] = nextFragment;
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
});
|
|
1135
|
+
if (Object.keys(changedFragments).length === 0) {
|
|
1136
|
+
return nextAction;
|
|
1137
|
+
}
|
|
1138
|
+
store.dispatch({
|
|
1139
|
+
type: UPDATE_FRAGMENTS,
|
|
1140
|
+
payload: {
|
|
1141
|
+
changedFragments
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
return nextAction;
|
|
1145
|
+
};
|
|
1146
|
+
|
|
1147
|
+
// lib/index.tsx
|
|
1148
|
+
function pageToInitialState(key, page) {
|
|
1149
|
+
const slices = page.slices || {};
|
|
1150
|
+
const nextPage = {
|
|
1151
|
+
...page,
|
|
1152
|
+
pageKey: key,
|
|
1153
|
+
//TODO remove this
|
|
1154
|
+
savedAt: Date.now()
|
|
1155
|
+
};
|
|
1156
|
+
return {
|
|
1157
|
+
pages: { [key]: nextPage },
|
|
1158
|
+
...slices
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
function start({
|
|
1162
|
+
initialPage,
|
|
1163
|
+
baseUrl = config.baseUrl,
|
|
1164
|
+
maxPages = config.maxPages,
|
|
1165
|
+
path
|
|
1166
|
+
}) {
|
|
1167
|
+
const initialPageKey = urlToPageKey((0, import_url_parse4.default)(path).href);
|
|
1168
|
+
const { csrfToken } = initialPage;
|
|
1169
|
+
const location = (0, import_url_parse4.default)(path);
|
|
1170
|
+
config.baseUrl = baseUrl;
|
|
1171
|
+
config.maxPages = maxPages;
|
|
1172
|
+
return {
|
|
1173
|
+
reducer: rootReducer,
|
|
1174
|
+
prepareStore: function(store) {
|
|
1175
|
+
store.dispatch({
|
|
1176
|
+
type: HISTORY_CHANGE,
|
|
1177
|
+
payload: {
|
|
1178
|
+
pathname: location.pathname,
|
|
1179
|
+
search: location.query,
|
|
1180
|
+
hash: location.hash
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
store.dispatch(saveAndProcessPage(initialPageKey, initialPage));
|
|
1184
|
+
store.dispatch({ type: SET_CSRF_TOKEN, payload: { csrfToken } });
|
|
1185
|
+
},
|
|
1186
|
+
initialState: pageToInitialState(initialPageKey, initialPage),
|
|
1187
|
+
initialPageKey
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
var ApplicationBase = class extends import_react2.default.Component {
|
|
1191
|
+
/**
|
|
1192
|
+
* The constructor of the `ApplicationBase` class.
|
|
1193
|
+
* @param props
|
|
1194
|
+
*/
|
|
1195
|
+
constructor(props) {
|
|
1196
|
+
super(props);
|
|
1197
|
+
this.hasWindow = typeof window !== "undefined";
|
|
1198
|
+
this.navigatorRef = import_react2.default.createRef();
|
|
1199
|
+
const { prepareStore, initialState, initialPageKey, reducer } = start({
|
|
1200
|
+
initialPage: this.props.initialPage,
|
|
1201
|
+
baseUrl: this.props.baseUrl,
|
|
1202
|
+
path: this.props.path
|
|
1203
|
+
// The max number of pages to keep in the store. Default is 20
|
|
1204
|
+
// maxPages: 20
|
|
1205
|
+
});
|
|
1206
|
+
this.initialPageKey = initialPageKey;
|
|
1207
|
+
this.store = this.buildStore(initialState, reducer);
|
|
1208
|
+
prepareStore(this.store);
|
|
1209
|
+
this.history = this.createHistory();
|
|
1210
|
+
this.history.replace(...argsForHistory(this.props.path));
|
|
1211
|
+
const unconnectedMapping = this.mapping();
|
|
1212
|
+
const nextMapping = {};
|
|
1213
|
+
for (const key in unconnectedMapping) {
|
|
1214
|
+
const component = unconnectedMapping[key];
|
|
1215
|
+
nextMapping[key] = (0, import_react_redux.connect)(mapStateToProps, mapDispatchToProps)(component);
|
|
1216
|
+
}
|
|
1217
|
+
this.connectedMapping = nextMapping;
|
|
1218
|
+
const { visit, remote: remote2 } = this.visitAndRemote(this.navigatorRef, this.store);
|
|
1219
|
+
this.visit = visit;
|
|
1220
|
+
this.remote = remote2;
|
|
1221
|
+
}
|
|
1222
|
+
componentDidMount() {
|
|
1223
|
+
const { appEl } = this.props;
|
|
1224
|
+
this.ujsHandlers = ujsHandlers({
|
|
1225
|
+
visit: this.visit,
|
|
1226
|
+
remote: this.remote,
|
|
1227
|
+
ujsAttributePrefix: "data-sg"
|
|
1228
|
+
});
|
|
1229
|
+
const { onClick, onSubmit } = this.ujsHandlers;
|
|
1230
|
+
appEl.addEventListener("click", onClick);
|
|
1231
|
+
appEl.addEventListener("submit", onSubmit);
|
|
1232
|
+
}
|
|
1233
|
+
componentWillUnmount() {
|
|
1234
|
+
const { appEl } = this.props;
|
|
1235
|
+
const { onClick, onSubmit } = this.ujsHandlers;
|
|
1236
|
+
appEl.removeEventListener("click", onClick);
|
|
1237
|
+
appEl.removeEventListener("submit", onSubmit);
|
|
1238
|
+
}
|
|
1239
|
+
createHistory() {
|
|
1240
|
+
if (this.hasWindow) {
|
|
1241
|
+
return (0, import_history.createBrowserHistory)({});
|
|
1242
|
+
} else {
|
|
1243
|
+
return (0, import_history.createMemoryHistory)({});
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
render() {
|
|
1247
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_react_redux.Provider, { store: this.store }, /* @__PURE__ */ import_react2.default.createElement(
|
|
1248
|
+
Nav_default,
|
|
1249
|
+
{
|
|
1250
|
+
store: this.store,
|
|
1251
|
+
ref: this.navigatorRef,
|
|
1252
|
+
visit: this.visit,
|
|
1253
|
+
remote: this.remote,
|
|
1254
|
+
mapping: this.connectedMapping,
|
|
1255
|
+
history: this.history,
|
|
1256
|
+
initialPageKey: this.initialPageKey
|
|
1257
|
+
}
|
|
1258
|
+
));
|
|
1259
|
+
}
|
|
1260
|
+
};
|
|
1261
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1262
|
+
0 && (module.exports = {
|
|
1263
|
+
ApplicationBase,
|
|
1264
|
+
BEFORE_FETCH,
|
|
1265
|
+
BEFORE_REMOTE,
|
|
1266
|
+
BEFORE_VISIT,
|
|
1267
|
+
COPY_PAGE,
|
|
1268
|
+
GRAFTING_ERROR,
|
|
1269
|
+
GRAFTING_SUCCESS,
|
|
1270
|
+
HISTORY_CHANGE,
|
|
1271
|
+
REMOVE_PAGE,
|
|
1272
|
+
SAVE_RESPONSE,
|
|
1273
|
+
UPDATE_FRAGMENTS,
|
|
1274
|
+
fragmentMiddleware,
|
|
1275
|
+
getIn,
|
|
1276
|
+
urlToPageKey
|
|
1277
|
+
});
|
|
1278
|
+
//# sourceMappingURL=superglue.cjs.map
|