-
Notifications
You must be signed in to change notification settings - Fork 2k
/
controller.js
285 lines (244 loc) · 9.84 KB
/
controller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
import { isEnabled } from '@automattic/calypso-config';
import { makeLayout, render } from 'calypso/controller';
import { addQueryArgs } from 'calypso/lib/route';
import { EDITOR_START, POST_EDIT } from 'calypso/state/action-types';
import { requestAdminMenu } from 'calypso/state/admin-menu/actions';
import { getAdminMenu, getIsRequestingAdminMenu } from 'calypso/state/admin-menu/selectors';
import { stopEditingPost } from 'calypso/state/editor/actions';
import { requestSelectedEditor } from 'calypso/state/selected-editor/actions';
import getEditorUrl from 'calypso/state/selectors/get-editor-url';
import { getSelectedEditor } from 'calypso/state/selectors/get-selected-editor';
import getSiteEditorUrl from 'calypso/state/selectors/get-site-editor-url';
import isAtomicSite from 'calypso/state/selectors/is-site-automated-transfer';
import shouldLoadGutenframe from 'calypso/state/selectors/should-load-gutenframe';
import { requestSite } from 'calypso/state/sites/actions';
import {
getSiteUrl,
getSiteOption,
isJetpackSite,
isSSOEnabled,
} from 'calypso/state/sites/selectors';
import { getSelectedSiteId } from 'calypso/state/ui/selectors';
import CalypsoifyIframe from './calypsoify-iframe';
import { Placeholder } from './placeholder';
const noop = () => {};
function determinePostType( context ) {
if ( context.path.startsWith( '/post/' ) ) {
return 'post';
}
if ( context.path.startsWith( '/page/' ) ) {
return 'page';
}
return context.params.customPostType;
}
function getPostID( context ) {
if ( ! context.params.post || 'new' === context.params.post ) {
return null;
}
if ( 'home' === context.params.post ) {
const state = context.store.getState();
const siteId = getSelectedSiteId( state );
return parseInt( getSiteOption( state, siteId, 'page_on_front' ), 10 );
}
// both post and site are in the path
return parseInt( context.params.post, 10 );
}
function waitForSiteIdAndSelectedEditor( context ) {
return new Promise( ( resolve ) => {
const unsubscribe = context.store.subscribe( () => {
const state = context.store.getState();
const siteId = getSelectedSiteId( state );
if ( ! siteId ) {
return;
}
const selectedEditor = getSelectedEditor( state, siteId );
if ( ! selectedEditor ) {
return;
}
unsubscribe();
resolve();
} );
// Trigger a `store.subscribe()` callback
context.store.dispatch(
requestSelectedEditor( getSelectedSiteId( context.store.getState() ) )
);
} );
}
function isPreferredEditorViewAvailable( state ) {
const siteId = getSelectedSiteId( state );
if ( ! siteId || getIsRequestingAdminMenu( state ) ) {
return false;
}
return null !== getAdminMenu( state, siteId );
}
function waitForPreferredEditorView( context ) {
return new Promise( ( resolve ) => {
const unsubscribe = context.store.subscribe( () => {
const state = context.store.getState();
if ( ! isPreferredEditorViewAvailable( state ) ) {
return;
}
unsubscribe();
resolve();
} );
// Trigger a `store.subscribe()` callback
context.store.dispatch( requestAdminMenu( getSelectedSiteId( context.store.getState() ) ) );
} );
}
/**
* Ensures the user is authenticated in WP Admin so the iframe can be loaded successfully.
*
* Simple sites users are always authenticated since the iframe is loaded through a *.wordpress.com URL (first-party
* cookie).
*
* Atomic and Jetpack sites will load the iframe through a different domain (third-party cookie). This can prevent the
* auth cookies from being stored while embedding WP Admin in Calypso (i.e. if the browser is preventing cross-site
* tracking), so we redirect the user to the WP Admin login page in order to store the auth cookie. Users will be
* redirected back to Calypso when they are authenticated in WP Admin.
*
* @param {object} context Shared context in the route.
* @param {Function} next Next registered callback for the route.
* @returns {*} Whatever the next callback returns.
*/
export const authenticate = ( context, next ) => {
const state = context.store.getState();
const siteId = getSelectedSiteId( state );
const isJetpack = isJetpackSite( state, siteId );
const isDesktop = isEnabled( 'desktop' );
const storageKey = `gutenframe_${ siteId }_is_authenticated`;
let isAuthenticated =
globalThis.sessionStorage.getItem( storageKey ) || // Previously authenticated.
! isJetpack || // If the site is not Jetpack (Atomic or self hosted) then it's a simple site and users are always authenticated.
( isJetpack && isSSOEnabled( state, siteId ) ) || // Assume we can authenticate with SSO
isDesktop || // The desktop app can store third-party cookies.
context.query.authWpAdmin; // Redirect back from the WP Admin login page to Calypso.
if ( isDesktop && isJetpack && ! isSSOEnabled( state, siteId ) ) {
isAuthenticated = false;
}
if ( isAuthenticated ) {
/*
* Make sure we have an up-to-date frame nonce.
*
* By requesting the site here instead of using <QuerySites /> we avoid a race condition, where
* if a render occurs before the site is requested, the first request for retrieving the iframe
* will get aborted.
*/
context.store.dispatch( requestSite( siteId ) );
globalThis.sessionStorage.setItem( storageKey, 'true' );
return next();
}
// Shows the editor placeholder while doing the redirection.
context.primary = <Placeholder />;
makeLayout( context, noop );
render( context );
// We could use `window.location.href` to generate the return URL but there are some potential race conditions that
// can cause the browser to not update it before redirecting to WP Admin. To avoid that, we manually generate the
// URL from the relevant parts.
const origin = window.location.origin;
const returnUrl = addQueryArgs(
{ ...context.query, authWpAdmin: true },
`${ origin }${ context.path }`
);
const siteUrl = getSiteUrl( state, siteId );
const wpAdminLoginUrl = addQueryArgs( { redirect_to: returnUrl }, `${ siteUrl }/wp-login.php` );
window.location.replace( wpAdminLoginUrl );
};
export const redirect = async ( context, next ) => {
const {
store: { getState },
} = context;
const tmpState = getState();
const selectedEditor = getSelectedEditor( tmpState, getSelectedSiteId( tmpState ) );
const checkPromises = [];
if ( ! selectedEditor ) {
checkPromises.push( waitForSiteIdAndSelectedEditor( context ) );
}
if ( ! isPreferredEditorViewAvailable( tmpState ) ) {
checkPromises.push( waitForPreferredEditorView( context ) );
}
await Promise.all( checkPromises );
const state = getState();
const siteId = getSelectedSiteId( state );
const isPostShare = context.query.is_post_share; // Added here https://github.com/Automattic/wp-calypso/blob/4b5fdb65b115e02baf743d2487eeca94fbd28a18/client/blocks/reader-share/index.jsx#L74
// Force load Gutenframe when choosing to share a post to a Simple site.
if ( isPostShare && isPostShare === 'true' && ! isAtomicSite( state, siteId ) ) {
return next();
}
const postType = determinePostType( context );
if ( ! shouldLoadGutenframe( state, siteId, postType ) ) {
const postId = getPostID( context );
const url = postType
? getEditorUrl( state, siteId, postId, postType )
: getSiteEditorUrl( state, siteId );
// pass along parameters, for example press-this
return window.location.replace( addQueryArgs( context.query, url ) );
}
return next();
};
function getPressThisData( query ) {
const { text, url, title, image, embed } = query;
return url ? { text, url, title, image, embed } : null;
}
function getAnchorFmData( query ) {
const { anchor_podcast, anchor_episode, spotify_url } = query;
return { anchor_podcast, anchor_episode, spotify_url };
}
function showDraftPostModal() {
const value = window.sessionStorage.getItem( 'wpcom_signup_complete_show_draft_post_modal' );
if ( value ) {
window.sessionStorage.removeItem( 'wpcom_signup_complete_show_draft_post_modal' );
}
return value;
}
export const post = ( context, next ) => {
const postId = getPostID( context );
const postType = determinePostType( context );
const jetpackCopy = parseInt( context.query[ 'jetpack-copy' ] );
// Check if this value is an integer.
const duplicatePostId = Number.isInteger( jetpackCopy ) ? jetpackCopy : null;
const state = context.store.getState();
const siteId = getSelectedSiteId( state );
const pressThisData = getPressThisData( context.query );
const anchorFmData = getAnchorFmData( context.query );
const parentPostId = parseInt( context.query.parent_post, 10 ) || null;
// Set postId on state.editor.postId, so components like editor revisions can read from it.
context.store.dispatch( { type: EDITOR_START, siteId, postId } );
// Set post type on state.posts.[ id ].type, so components like document head can read from it.
context.store.dispatch( { type: POST_EDIT, post: { type: postType }, siteId, postId } );
context.primary = (
<CalypsoifyIframe
key={ postId }
postId={ postId }
postType={ postType }
duplicatePostId={ duplicatePostId }
pressThisData={ pressThisData }
anchorFmData={ anchorFmData }
parentPostId={ parentPostId }
creatingNewHomepage={ postType === 'page' && context.query.hasOwnProperty( 'new-homepage' ) }
stripeConnectSuccess={ context.query.stripe_connect_success ?? null }
showDraftPostModal={ showDraftPostModal() }
/>
);
return next();
};
export const siteEditor = ( context, next ) => {
const state = context.store.getState();
const siteId = getSelectedSiteId( state );
context.primary = (
<CalypsoifyIframe
// This key is added as a precaution due to it's oberserved necessity in the above post editor.
// It will force the component to remount completely when the Id changes.
key={ siteId }
editorType={ 'site' }
/>
);
return next();
};
export const exitPost = ( context, next ) => {
const postId = getPostID( context );
const siteId = getSelectedSiteId( context.store.getState() );
if ( siteId ) {
context.store.dispatch( stopEditingPost( siteId, postId ) );
}
next();
};