@thoughtbot/superglue 0.0.0 → 0.30.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/README.md +122 -0
- package/action_creators/index.js +160 -0
- package/action_creators/requests.js +207 -0
- package/actions.js +28 -0
- package/components/Nav.js +186 -0
- package/components/RailsTag.js +71 -0
- package/config.js +9 -0
- package/index.js +263 -0
- package/middleware.js +80 -0
- package/package.json +48 -6
- package/reducers/index.js +288 -0
- package/utils/helpers.js +36 -0
- package/utils/immutability.js +176 -0
- package/utils/index.js +59 -0
- package/utils/react.js +52 -0
- package/utils/request.js +132 -0
- package/utils/ujs.js +138 -0
- package/utils/url.js +126 -0
- package/utils/window.js +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Superglue
|
|
2
|
+
|
|
3
|
+
Use classic Rails to build rich React Redux applications with **NO APIs** and
|
|
4
|
+
**NO client-side routing**.
|
|
5
|
+
|
|
6
|
+
[](https://circleci.com/gh/thoughtbot/superglue)
|
|
7
|
+
|
|
8
|
+
Superglue is a React Redux starter and library inspired by Turbolinks and designed
|
|
9
|
+
to complement classic Rails. You can enjoy the benefits of Redux state
|
|
10
|
+
management and React components without giving up the productivity of Rails form
|
|
11
|
+
helpers, UJS, tag helpers, the flash, cookie auth, and more.
|
|
12
|
+
|
|
13
|
+
## Caution
|
|
14
|
+
|
|
15
|
+
This project is in its early phases of development. Its interface, behavior,
|
|
16
|
+
and name are likely to change drastically before a major version release.
|
|
17
|
+
|
|
18
|
+
### No APIs
|
|
19
|
+
|
|
20
|
+
Instead of APIs, Superglue leans on Rail's ability to respond to different
|
|
21
|
+
[mime types](https://apidock.com/rails/ActionController/MimeResponds/InstanceMethods/respond_to)
|
|
22
|
+
on the same route. In a Superglue application, if you direct your browser to
|
|
23
|
+
`/dashboard.html`, you would see the HTML version of the content, and if you
|
|
24
|
+
went to `/dashboard.json` you would see the JSON version of the exact same
|
|
25
|
+
content down to the footer.
|
|
26
|
+
|
|
27
|
+
The end result would be something like this:
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
### Powered by Classic Rails
|
|
32
|
+
Superglue is mostly classic Rails. Features like the flash, cookie auth, and URL
|
|
33
|
+
helpers continue to be useful. Here's a look at the directory structure of a
|
|
34
|
+
typical Rails application with Superglue.
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
MyRailsApp/
|
|
38
|
+
app/
|
|
39
|
+
views/
|
|
40
|
+
dashboard/
|
|
41
|
+
index.html.erb <- Mostly empty. Where `index.json.props` gets rendered as initial state
|
|
42
|
+
index.js <- Your page component, will receive `index.json.props`. Gets packaged with application.js
|
|
43
|
+
index.json.props <- will also respond to `.json` requests
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### PropsTemplate
|
|
47
|
+
Powering these JSON responses is PropsTemplate, a traversable JSON templating DSL
|
|
48
|
+
inspired by JBuilder. With PropsTemplate you can specify a path of the node you
|
|
49
|
+
want, and PropsTemplate will walk the tree to it, skipping the execution of nodes
|
|
50
|
+
that don't match the keypath.
|
|
51
|
+
|
|
52
|
+

|
|
53
|
+
|
|
54
|
+
### All together now!
|
|
55
|
+
Superglue comes with batteries that bring all the above concepts together to make
|
|
56
|
+
building popular SPA features easy, painless, and productive.
|
|
57
|
+
|
|
58
|
+
#### SPA Navigation
|
|
59
|
+
A popular ask of SPAs is page-to-page navigation without reloading. This is
|
|
60
|
+
easily done with Superglue's own UJS attributes inspired by Turbolinks:
|
|
61
|
+
|
|
62
|
+
```jsx
|
|
63
|
+
<a href='/posts' data-bz-visit={true} />
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The above will request for `/posts` with an `accept` of `application/json`, and
|
|
67
|
+
when the client receives the response, swap out the current component for the
|
|
68
|
+
component the response asks for, and `pushState` on history.
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
#### Partial updates
|
|
72
|
+
Some features rely on updating some parts of the existing page. In
|
|
73
|
+
addition to `data-bz-visit` and it's equivalent `this.props.visit`, Superglue
|
|
74
|
+
also provides `data-bz-remote` or `this.props.remote`, which you can use to
|
|
75
|
+
update parts of your page in async fashion without changing `window.history`.
|
|
76
|
+
|
|
77
|
+
Imagine having to implement search, where you enter some text, hit enter, and
|
|
78
|
+
results would show without reloading the screen. In traditional applications,
|
|
79
|
+
you may need a new controller, routes, a discussion over versioning, JSON
|
|
80
|
+
serializer, plenty of new JS code, etc.
|
|
81
|
+
|
|
82
|
+

|
|
83
|
+
|
|
84
|
+
With Superglue, this can be done in one line:
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
this.props.remote('/dashboard?qry=haircut&props_at=data.header.search')
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The above will make a request to `/dashboard?qry=haircut`, walk your props to
|
|
91
|
+
the `data.header.search` node, return it in the response, and immutably graft it
|
|
92
|
+
in the exact same path on the redux store before finally letting React
|
|
93
|
+
re-render.
|
|
94
|
+
|
|
95
|
+
For more on what you can do, check out our documentation.
|
|
96
|
+
|
|
97
|
+
#### Server-Side Rendering
|
|
98
|
+
Server-Side Rendering is supported via [Humid](https://github.com/thoughtbot/humid).
|
|
99
|
+
See the [documentation for server-side rendering][ssr docs].
|
|
100
|
+
|
|
101
|
+
[ssr docs]: ./recipes/server-side-rendering.md
|
|
102
|
+
|
|
103
|
+
## Documentation
|
|
104
|
+
|
|
105
|
+
Documentation is hosted on [Github pages](https://thoughtbot.github.io/superglue).
|
|
106
|
+
|
|
107
|
+
## Contributing
|
|
108
|
+
|
|
109
|
+
See the [CONTRIBUTING] document. Thank you, [contributors]!
|
|
110
|
+
|
|
111
|
+
[CONTRIBUTING]: CONTRIBUTING.md
|
|
112
|
+
[contributors]: https://github.com/thoughtbot/superglue/graphs/contributors
|
|
113
|
+
|
|
114
|
+
## Special Thanks
|
|
115
|
+
|
|
116
|
+
Thanks to [jbuilder](https://github.com/rails/jbuilder),
|
|
117
|
+
[scour](https://github.com/rstacruz/scour),
|
|
118
|
+
[turbolinks3](https://github.com/turbolinks/turbolinks-classic),
|
|
119
|
+
[turbograft](https://github.com/Shopify/turbograft/),
|
|
120
|
+
[turbostreamer](https://github.com/malomalo/turbostreamer)
|
|
121
|
+
|
|
122
|
+
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
var _exportNames = {
|
|
5
|
+
copyPage: true,
|
|
6
|
+
saveResponse: true,
|
|
7
|
+
handleGraft: true,
|
|
8
|
+
saveAndProcessPage: true
|
|
9
|
+
};
|
|
10
|
+
exports.copyPage = copyPage;
|
|
11
|
+
exports.handleGraft = handleGraft;
|
|
12
|
+
exports.saveAndProcessPage = saveAndProcessPage;
|
|
13
|
+
exports.saveResponse = saveResponse;
|
|
14
|
+
|
|
15
|
+
var _utils = require("../utils");
|
|
16
|
+
|
|
17
|
+
var _urlParse = _interopRequireDefault(require("url-parse"));
|
|
18
|
+
|
|
19
|
+
var _actions = require("../actions");
|
|
20
|
+
|
|
21
|
+
var _requests = require("./requests");
|
|
22
|
+
|
|
23
|
+
Object.keys(_requests).forEach(function (key) {
|
|
24
|
+
if (key === "default" || key === "__esModule") return;
|
|
25
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
26
|
+
if (key in exports && exports[key] === _requests[key]) return;
|
|
27
|
+
exports[key] = _requests[key];
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
|
|
31
|
+
|
|
32
|
+
function copyPage(_ref) {
|
|
33
|
+
var from = _ref.from,
|
|
34
|
+
to = _ref.to;
|
|
35
|
+
return {
|
|
36
|
+
type: _actions.COPY_PAGE,
|
|
37
|
+
payload: {
|
|
38
|
+
from: from,
|
|
39
|
+
to: to
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function saveResponse(_ref2) {
|
|
45
|
+
var pageKey = _ref2.pageKey,
|
|
46
|
+
page = _ref2.page;
|
|
47
|
+
pageKey = (0, _utils.urlToPageKey)(pageKey);
|
|
48
|
+
return {
|
|
49
|
+
type: _actions.SAVE_RESPONSE,
|
|
50
|
+
payload: {
|
|
51
|
+
pageKey: pageKey,
|
|
52
|
+
page: page
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function handleGraft(_ref3) {
|
|
58
|
+
var pageKey = _ref3.pageKey,
|
|
59
|
+
page = _ref3.page;
|
|
60
|
+
pageKey = (0, _utils.urlToPageKey)(pageKey);
|
|
61
|
+
return {
|
|
62
|
+
type: _actions.HANDLE_GRAFT,
|
|
63
|
+
payload: {
|
|
64
|
+
pageKey: pageKey,
|
|
65
|
+
page: page
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function fetchDeferments(pageKey, defers) {
|
|
71
|
+
if (defers === void 0) {
|
|
72
|
+
defers = [];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
pageKey = (0, _utils.urlToPageKey)(pageKey);
|
|
76
|
+
return function (dispatch) {
|
|
77
|
+
var fetches = defers.filter(function (_ref4) {
|
|
78
|
+
var type = _ref4.type;
|
|
79
|
+
return type === 'auto';
|
|
80
|
+
}).map(function (_ref5) {
|
|
81
|
+
var url = _ref5.url,
|
|
82
|
+
_ref5$successAction = _ref5.successAction,
|
|
83
|
+
successAction = _ref5$successAction === void 0 ? _actions.GRAFTING_SUCCESS : _ref5$successAction,
|
|
84
|
+
_ref5$failAction = _ref5.failAction,
|
|
85
|
+
failAction = _ref5$failAction === void 0 ? _actions.GRAFTING_ERROR : _ref5$failAction;
|
|
86
|
+
var parsedUrl = new _urlParse["default"](url, true);
|
|
87
|
+
var keyPath = parsedUrl.query.props_at;
|
|
88
|
+
return dispatch((0, _requests.remote)(url, {
|
|
89
|
+
pageKey: pageKey
|
|
90
|
+
})).then(function () {
|
|
91
|
+
dispatch({
|
|
92
|
+
type: successAction,
|
|
93
|
+
payload: {
|
|
94
|
+
pageKey: pageKey,
|
|
95
|
+
keyPath: keyPath
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
})["catch"](function (err) {
|
|
99
|
+
dispatch({
|
|
100
|
+
type: failAction,
|
|
101
|
+
payload: {
|
|
102
|
+
url: url,
|
|
103
|
+
err: err,
|
|
104
|
+
pageKey: pageKey,
|
|
105
|
+
keyPath: keyPath
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
return Promise.all(fetches);
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function updateFragmentsUsing(page) {
|
|
115
|
+
var changedFragments = {};
|
|
116
|
+
page.fragments.forEach(function (fragment) {
|
|
117
|
+
var type = fragment.type,
|
|
118
|
+
path = fragment.path;
|
|
119
|
+
changedFragments[type] = (0, _utils.getIn)(page, path);
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
type: _actions.UPDATE_FRAGMENTS,
|
|
123
|
+
payload: {
|
|
124
|
+
changedFragments: changedFragments
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function saveAndProcessPage(pageKey, page) {
|
|
130
|
+
return function (dispatch, getState) {
|
|
131
|
+
pageKey = (0, _utils.urlToPageKey)(pageKey);
|
|
132
|
+
var _page$defers = page.defers,
|
|
133
|
+
defers = _page$defers === void 0 ? [] : _page$defers;
|
|
134
|
+
|
|
135
|
+
if ((0, _utils.isGraft)(page)) {
|
|
136
|
+
dispatch(handleGraft({
|
|
137
|
+
pageKey: pageKey,
|
|
138
|
+
page: page
|
|
139
|
+
}));
|
|
140
|
+
} else {
|
|
141
|
+
dispatch(saveResponse({
|
|
142
|
+
pageKey: pageKey,
|
|
143
|
+
page: page
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
var hasFetch = typeof fetch != 'undefined';
|
|
148
|
+
|
|
149
|
+
if (hasFetch) {
|
|
150
|
+
return dispatch(fetchDeferments(pageKey, defers)).then(function () {
|
|
151
|
+
if (page.fragments.length > 0) {
|
|
152
|
+
var finishedPage = getState().pages[pageKey];
|
|
153
|
+
return dispatch(updateFragmentsUsing(finishedPage));
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
} else {
|
|
157
|
+
return Promise.resolve();
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.clearFlash = clearFlash;
|
|
5
|
+
exports.remote = remote;
|
|
6
|
+
exports.visit = visit;
|
|
7
|
+
|
|
8
|
+
var _utils = require("../utils");
|
|
9
|
+
|
|
10
|
+
var _actions = require("../actions");
|
|
11
|
+
|
|
12
|
+
var _index = require("./index");
|
|
13
|
+
|
|
14
|
+
function beforeFetch(payload) {
|
|
15
|
+
return {
|
|
16
|
+
type: _actions.BEFORE_FETCH,
|
|
17
|
+
payload: payload
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function handleError(err) {
|
|
22
|
+
return {
|
|
23
|
+
type: _actions.SUPERGLUE_ERROR,
|
|
24
|
+
payload: {
|
|
25
|
+
message: err.message
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function handleFetchErr(err, fetchArgs, dispatch) {
|
|
31
|
+
err.fetchArgs = fetchArgs;
|
|
32
|
+
err.url = fetchArgs[0];
|
|
33
|
+
err.pageKey = (0, _utils.urlToPageKey)(fetchArgs[0]);
|
|
34
|
+
dispatch(handleError(err));
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildMeta(pageKey, page, state) {
|
|
39
|
+
var prevAssets = state.assets;
|
|
40
|
+
var nextAssets = page.assets;
|
|
41
|
+
return {
|
|
42
|
+
pageKey: pageKey,
|
|
43
|
+
page: page,
|
|
44
|
+
componentIdentifier: page.componentIdentifier,
|
|
45
|
+
needsRefresh: (0, _utils.needsRefresh)(prevAssets, nextAssets)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function clearFlash(_ref) {
|
|
50
|
+
var pageKey = _ref.pageKey;
|
|
51
|
+
return {
|
|
52
|
+
type: _actions.CLEAR_FLASH,
|
|
53
|
+
payload: {
|
|
54
|
+
pageKey: pageKey
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function remote(path, _temp) {
|
|
60
|
+
var _ref2 = _temp === void 0 ? {} : _temp,
|
|
61
|
+
_ref2$method = _ref2.method,
|
|
62
|
+
method = _ref2$method === void 0 ? 'GET' : _ref2$method,
|
|
63
|
+
headers = _ref2.headers,
|
|
64
|
+
_ref2$body = _ref2.body,
|
|
65
|
+
body = _ref2$body === void 0 ? '' : _ref2$body,
|
|
66
|
+
pageKey = _ref2.pageKey,
|
|
67
|
+
_ref2$beforeSave = _ref2.beforeSave,
|
|
68
|
+
beforeSave = _ref2$beforeSave === void 0 ? function (prevPage, receivedPage) {
|
|
69
|
+
return receivedPage;
|
|
70
|
+
} : _ref2$beforeSave;
|
|
71
|
+
|
|
72
|
+
path = (0, _utils.withoutBusters)(path);
|
|
73
|
+
pageKey = pageKey && (0, _utils.urlToPageKey)(pageKey);
|
|
74
|
+
return function (dispatch, getState) {
|
|
75
|
+
var fetchArgs = (0, _utils.argsForFetch)(getState, path, {
|
|
76
|
+
method: method,
|
|
77
|
+
headers: headers,
|
|
78
|
+
body: body
|
|
79
|
+
});
|
|
80
|
+
pageKey = pageKey || getState().superglue.currentPageKey;
|
|
81
|
+
dispatch(beforeFetch({
|
|
82
|
+
fetchArgs: fetchArgs
|
|
83
|
+
}));
|
|
84
|
+
return fetch.apply(void 0, fetchArgs).then(_utils.parseResponse).then(function (_ref3) {
|
|
85
|
+
var rsp = _ref3.rsp,
|
|
86
|
+
json = _ref3.json;
|
|
87
|
+
|
|
88
|
+
var _getState = getState(),
|
|
89
|
+
superglue = _getState.superglue,
|
|
90
|
+
_getState$pages = _getState.pages,
|
|
91
|
+
pages = _getState$pages === void 0 ? {} : _getState$pages;
|
|
92
|
+
|
|
93
|
+
var meta = Object.assign({}, buildMeta(pageKey, json, superglue), {
|
|
94
|
+
redirected: rsp.redirected,
|
|
95
|
+
rsp: rsp,
|
|
96
|
+
fetchArgs: fetchArgs
|
|
97
|
+
});
|
|
98
|
+
var page = beforeSave(pages[pageKey], json);
|
|
99
|
+
return dispatch((0, _index.saveAndProcessPage)(pageKey, page)).then(function () {
|
|
100
|
+
meta.pageKey = pageKey;
|
|
101
|
+
return meta;
|
|
102
|
+
});
|
|
103
|
+
})["catch"](function (e) {
|
|
104
|
+
return handleFetchErr(e, fetchArgs, dispatch);
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
var lastVisitController = {
|
|
110
|
+
abort: function abort() {}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
function visit(path, _temp2) {
|
|
114
|
+
var _ref4 = _temp2 === void 0 ? {} : _temp2,
|
|
115
|
+
_ref4$method = _ref4.method,
|
|
116
|
+
method = _ref4$method === void 0 ? 'GET' : _ref4$method,
|
|
117
|
+
headers = _ref4.headers,
|
|
118
|
+
_ref4$body = _ref4.body,
|
|
119
|
+
body = _ref4$body === void 0 ? '' : _ref4$body,
|
|
120
|
+
placeholderKey = _ref4.placeholderKey,
|
|
121
|
+
_ref4$beforeSave = _ref4.beforeSave,
|
|
122
|
+
beforeSave = _ref4$beforeSave === void 0 ? function (prevPage, receivedPage) {
|
|
123
|
+
return receivedPage;
|
|
124
|
+
} : _ref4$beforeSave;
|
|
125
|
+
|
|
126
|
+
path = (0, _utils.withoutBusters)(path);
|
|
127
|
+
var pageKey = (0, _utils.urlToPageKey)(path);
|
|
128
|
+
return function (dispatch, getState) {
|
|
129
|
+
var currentKey = getState().superglue.currentPageKey;
|
|
130
|
+
dispatch(clearFlash({
|
|
131
|
+
pageKey: currentKey
|
|
132
|
+
}));
|
|
133
|
+
placeholderKey = placeholderKey && (0, _utils.urlToPageKey)(placeholderKey);
|
|
134
|
+
var hasPlaceholder = !!getState().pages[placeholderKey];
|
|
135
|
+
|
|
136
|
+
if (placeholderKey && hasPlaceholder) {
|
|
137
|
+
dispatch((0, _index.copyPage)({
|
|
138
|
+
from: placeholderKey,
|
|
139
|
+
to: pageKey
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (placeholderKey && !hasPlaceholder) {
|
|
144
|
+
console.warn("Could not find placeholder with key " + placeholderKey + " in state. The props_at param will be ignored");
|
|
145
|
+
path = (0, _utils.removePropsAt)(path);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!placeholderKey && (0, _utils.hasPropsAt)(path)) {
|
|
149
|
+
console.warn("visit was called with props_at param in the path " + path + ", this will be ignore unless you provide a placeholder.");
|
|
150
|
+
path = (0, _utils.removePropsAt)(path);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
var controller = new AbortController();
|
|
154
|
+
var signal = controller.signal;
|
|
155
|
+
var fetchArgs = (0, _utils.argsForFetch)(getState, path, {
|
|
156
|
+
headers: headers,
|
|
157
|
+
body: body,
|
|
158
|
+
method: method,
|
|
159
|
+
signal: signal
|
|
160
|
+
});
|
|
161
|
+
dispatch(beforeFetch({
|
|
162
|
+
fetchArgs: fetchArgs
|
|
163
|
+
}));
|
|
164
|
+
lastVisitController.abort();
|
|
165
|
+
lastVisitController = controller;
|
|
166
|
+
return fetch.apply(void 0, fetchArgs).then(_utils.parseResponse).then(function (_ref5) {
|
|
167
|
+
var rsp = _ref5.rsp,
|
|
168
|
+
json = _ref5.json;
|
|
169
|
+
|
|
170
|
+
var _getState2 = getState(),
|
|
171
|
+
superglue = _getState2.superglue,
|
|
172
|
+
_getState2$pages = _getState2.pages,
|
|
173
|
+
pages = _getState2$pages === void 0 ? {} : _getState2$pages;
|
|
174
|
+
|
|
175
|
+
var meta = Object.assign({}, buildMeta(pageKey, json, superglue), {
|
|
176
|
+
redirected: rsp.redirected,
|
|
177
|
+
rsp: rsp,
|
|
178
|
+
fetchArgs: fetchArgs
|
|
179
|
+
});
|
|
180
|
+
meta.suggestedAction = 'push';
|
|
181
|
+
|
|
182
|
+
if (!rsp.redirected && fetchArgs[1].method != 'GET') {
|
|
183
|
+
meta.suggestedAction = 'replace';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (method !== 'GET') {
|
|
187
|
+
var contentLocation = rsp.headers.get('content-location');
|
|
188
|
+
|
|
189
|
+
if (contentLocation) {
|
|
190
|
+
pageKey = (0, _utils.urlToPageKey)(contentLocation);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (rsp.redirected) {
|
|
195
|
+
pageKey = (0, _utils.urlToPageKey)(rsp.url);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
var page = beforeSave(pages[pageKey], json);
|
|
199
|
+
return dispatch((0, _index.saveAndProcessPage)(pageKey, page)).then(function () {
|
|
200
|
+
meta.pageKey = pageKey;
|
|
201
|
+
return meta;
|
|
202
|
+
});
|
|
203
|
+
})["catch"](function (e) {
|
|
204
|
+
return handleFetchErr(e, fetchArgs, dispatch);
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
}
|
package/actions.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.UPDATE_FRAGMENTS = exports.SUPERGLUE_ERROR = exports.SET_CSRF_TOKEN = exports.SAVE_RESPONSE = exports.REMOVE_PAGE = exports.HISTORY_CHANGE = exports.HANDLE_GRAFT = exports.GRAFTING_SUCCESS = exports.GRAFTING_ERROR = exports.COPY_PAGE = exports.CLEAR_FLASH = exports.BEFORE_FETCH = void 0;
|
|
5
|
+
var BEFORE_FETCH = '@@superglue/BEFORE_FETCH';
|
|
6
|
+
exports.BEFORE_FETCH = BEFORE_FETCH;
|
|
7
|
+
var SAVE_RESPONSE = '@@superglue/SAVE_RESPONSE';
|
|
8
|
+
exports.SAVE_RESPONSE = SAVE_RESPONSE;
|
|
9
|
+
var HANDLE_GRAFT = '@@superglue/HANDLE_GRAFT';
|
|
10
|
+
exports.HANDLE_GRAFT = HANDLE_GRAFT;
|
|
11
|
+
var CLEAR_FLASH = '@@superglue/CLEAR_FLASH';
|
|
12
|
+
exports.CLEAR_FLASH = CLEAR_FLASH;
|
|
13
|
+
var SUPERGLUE_ERROR = '@@superglue/ERROR';
|
|
14
|
+
exports.SUPERGLUE_ERROR = SUPERGLUE_ERROR;
|
|
15
|
+
var GRAFTING_ERROR = '@@superglue/GRAFTING_ERROR';
|
|
16
|
+
exports.GRAFTING_ERROR = GRAFTING_ERROR;
|
|
17
|
+
var GRAFTING_SUCCESS = '@@superglue/GRAFTING_SUCCESS';
|
|
18
|
+
exports.GRAFTING_SUCCESS = GRAFTING_SUCCESS;
|
|
19
|
+
var HISTORY_CHANGE = '@@superglue/HISTORY_CHANGE';
|
|
20
|
+
exports.HISTORY_CHANGE = HISTORY_CHANGE;
|
|
21
|
+
var SET_CSRF_TOKEN = '@@superglue/SET_CSRF_TOKEN';
|
|
22
|
+
exports.SET_CSRF_TOKEN = SET_CSRF_TOKEN;
|
|
23
|
+
var REMOVE_PAGE = '@@superglue/REMOVE_PAGE';
|
|
24
|
+
exports.REMOVE_PAGE = REMOVE_PAGE;
|
|
25
|
+
var COPY_PAGE = '@@superglue/COPY_PAGE';
|
|
26
|
+
exports.COPY_PAGE = COPY_PAGE;
|
|
27
|
+
var UPDATE_FRAGMENTS = '@@superglue/UPDATE_FRAGMENTS';
|
|
28
|
+
exports.UPDATE_FRAGMENTS = UPDATE_FRAGMENTS;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports["default"] = void 0;
|
|
5
|
+
|
|
6
|
+
var _react = _interopRequireDefault(require("react"));
|
|
7
|
+
|
|
8
|
+
var _utils = require("../utils");
|
|
9
|
+
|
|
10
|
+
var _actions = require("../actions");
|
|
11
|
+
|
|
12
|
+
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
13
|
+
|
|
14
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
|
|
15
|
+
|
|
16
|
+
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
17
|
+
|
|
18
|
+
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
|
|
19
|
+
|
|
20
|
+
function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }
|
|
21
|
+
|
|
22
|
+
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
|
|
23
|
+
|
|
24
|
+
var Nav = /*#__PURE__*/function (_React$Component) {
|
|
25
|
+
_inheritsLoose(Nav, _React$Component);
|
|
26
|
+
|
|
27
|
+
function Nav(props) {
|
|
28
|
+
var _this;
|
|
29
|
+
|
|
30
|
+
_this = _React$Component.call(this, props) || this;
|
|
31
|
+
var _this$props = _this.props,
|
|
32
|
+
history = _this$props.history,
|
|
33
|
+
initialPageKey = _this$props.initialPageKey;
|
|
34
|
+
_this.history = history;
|
|
35
|
+
_this.navigateTo = _this.navigateTo.bind(_assertThisInitialized(_this));
|
|
36
|
+
_this.onHistoryChange = _this.onHistoryChange.bind(_assertThisInitialized(_this));
|
|
37
|
+
_this.state = {
|
|
38
|
+
pageKey: initialPageKey,
|
|
39
|
+
ownProps: {}
|
|
40
|
+
};
|
|
41
|
+
return _this;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var _proto = Nav.prototype;
|
|
45
|
+
|
|
46
|
+
_proto.componentDidMount = function componentDidMount() {
|
|
47
|
+
this.unsubscribeHistory = this.history.listen(this.onHistoryChange);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
_proto.componentWillUnmount = function componentWillUnmount() {
|
|
51
|
+
this.unsubscribeHistory();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
_proto.navigateTo = function navigateTo(path, _temp) {
|
|
55
|
+
var _ref = _temp === void 0 ? {
|
|
56
|
+
action: 'push',
|
|
57
|
+
ownProps: {}
|
|
58
|
+
} : _temp,
|
|
59
|
+
action = _ref.action,
|
|
60
|
+
ownProps = _ref.ownProps;
|
|
61
|
+
|
|
62
|
+
path = (0, _utils.pathWithoutBZParams)(path);
|
|
63
|
+
var nextPageKey = (0, _utils.urlToPageKey)(path);
|
|
64
|
+
var store = this.props.store;
|
|
65
|
+
var hasPage = !!store.getState().pages[nextPageKey];
|
|
66
|
+
|
|
67
|
+
if (hasPage) {
|
|
68
|
+
var prevPageKey = this.history.location.state.pageKey;
|
|
69
|
+
var historyArgs = [path, {
|
|
70
|
+
pageKey: nextPageKey,
|
|
71
|
+
superglue: true
|
|
72
|
+
}];
|
|
73
|
+
|
|
74
|
+
if (action === 'push') {
|
|
75
|
+
var _this$history;
|
|
76
|
+
|
|
77
|
+
(_this$history = this.history).push.apply(_this$history, historyArgs);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (action === 'replace') {
|
|
81
|
+
var _this$history2;
|
|
82
|
+
|
|
83
|
+
(_this$history2 = this.history).replace.apply(_this$history2, historyArgs);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.setState({
|
|
87
|
+
pageKey: nextPageKey,
|
|
88
|
+
ownProps: ownProps
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (action === 'replace' && prevPageKey && prevPageKey !== nextPageKey) {
|
|
92
|
+
store.dispatch({
|
|
93
|
+
type: _actions.REMOVE_PAGE,
|
|
94
|
+
payload: {
|
|
95
|
+
pageKey: prevPageKey
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return true;
|
|
101
|
+
} else {
|
|
102
|
+
console.warn("`navigateTo` was called , but could not find.\n the pageKey in the store. This may happen when the wrong\n content_location was set in your non-get controller action.\n No navigation will take place");
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
_proto.onHistoryChange = function onHistoryChange(location) {
|
|
108
|
+
var store = this.props.store;
|
|
109
|
+
var pathname = location.pathname,
|
|
110
|
+
search = location.search,
|
|
111
|
+
hash = location.hash,
|
|
112
|
+
state = location.state;
|
|
113
|
+
|
|
114
|
+
if (state && state.superglue) {
|
|
115
|
+
store.dispatch({
|
|
116
|
+
type: _actions.HISTORY_CHANGE,
|
|
117
|
+
payload: {
|
|
118
|
+
pathname: pathname,
|
|
119
|
+
search: search,
|
|
120
|
+
hash: hash
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
var pageKey = state.pageKey;
|
|
124
|
+
var containsKey = !!store.getState().pages[pageKey];
|
|
125
|
+
|
|
126
|
+
if (containsKey) {
|
|
127
|
+
this.setState({
|
|
128
|
+
pageKey: pageKey
|
|
129
|
+
});
|
|
130
|
+
} else {
|
|
131
|
+
this.reloadPage();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
_proto.reloadPage = function reloadPage() {
|
|
137
|
+
window.location.reload();
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
_proto.notFound = function notFound(identifier) {
|
|
141
|
+
var reminder = '';
|
|
142
|
+
|
|
143
|
+
if (!identifier) {
|
|
144
|
+
reminder = 'Did you forget to add `json.component_identifier` in your application.json.props layout?';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
var error = new Error("Superglue Nav component was looking for " + identifier + " but could not find it in your mapping. " + reminder);
|
|
148
|
+
throw error;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
_proto.render = function render() {
|
|
152
|
+
var _this$props2 = this.props,
|
|
153
|
+
store = _this$props2.store,
|
|
154
|
+
visit = _this$props2.visit,
|
|
155
|
+
remote = _this$props2.remote;
|
|
156
|
+
var _this$state = this.state,
|
|
157
|
+
pageKey = _this$state.pageKey,
|
|
158
|
+
ownProps = _this$state.ownProps;
|
|
159
|
+
var componentIdentifier = store.getState().pages[pageKey].componentIdentifier;
|
|
160
|
+
var Component = this.props.mapping[componentIdentifier];
|
|
161
|
+
|
|
162
|
+
if (Component) {
|
|
163
|
+
return /*#__PURE__*/_react["default"].createElement(Component, _extends({
|
|
164
|
+
pageKey: pageKey,
|
|
165
|
+
navigateTo: this.navigateTo,
|
|
166
|
+
visit: visit,
|
|
167
|
+
remote: remote
|
|
168
|
+
}, ownProps));
|
|
169
|
+
} else {
|
|
170
|
+
this.notFound(componentIdentifier);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
return Nav;
|
|
175
|
+
}(_react["default"].Component);
|
|
176
|
+
|
|
177
|
+
Nav.propTypes = {
|
|
178
|
+
store: _propTypes["default"].object,
|
|
179
|
+
history: _propTypes["default"].object,
|
|
180
|
+
mapping: _propTypes["default"].object,
|
|
181
|
+
visit: _propTypes["default"].func,
|
|
182
|
+
remote: _propTypes["default"].func,
|
|
183
|
+
initialPageKey: _propTypes["default"].string
|
|
184
|
+
};
|
|
185
|
+
var _default = Nav;
|
|
186
|
+
exports["default"] = _default;
|