From e54a196ae83e16220f8e252513ac13d6e8f407b1 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Thu, 2 Jan 2020 16:02:48 +0100 Subject: [PATCH 1/4] Add basic files for PoC. --- lib/load.php | 1 + lib/offline-editing.js | 49 +++++++++++++++++++ lib/offline-editing.php | 17 +++++++ packages/api-fetch/src/index.js | 6 +-- packages/api-fetch/src/utils/response.js | 4 ++ packages/editor/src/store/actions.js | 16 +++++- .../editor/src/store/utils/notice-builder.js | 14 ++++++ 7 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 lib/offline-editing.js create mode 100644 lib/offline-editing.php diff --git a/lib/load.php b/lib/load.php index 7076be29f6312..ce27d53d09209 100644 --- a/lib/load.php +++ b/lib/load.php @@ -64,3 +64,4 @@ function gutenberg_is_experiment_enabled( $name ) { require dirname( __FILE__ ) . '/experiments-page.php'; require dirname( __FILE__ ) . '/customizer.php'; require dirname( __FILE__ ) . '/edit-site-page.php'; +require dirname( __FILE__ ) . '/offline-editing.php'; diff --git a/lib/offline-editing.js b/lib/offline-editing.js new file mode 100644 index 0000000000000..305bbd1a31177 --- /dev/null +++ b/lib/offline-editing.js @@ -0,0 +1,49 @@ +/* global wp, fetch, caches, ERROR_OFFLINE_URL, ERROR_MESSAGES, ERROR_500_URL */ + +// IIFE is used for lexical scoping instead of just a braces block due to bug with const in Safari. +( () => { + const queue = new wp.serviceWorker.backgroundSync.Queue( 'gutenbergPendingEdits' ); + const errorMessages = {}; + + const editPostHandler = ( { url, event } ) => { + const clone = event.request.clone(); + return fetch( event.request ) + .then( ( response ) => { + return response; + } ) + .catch( () => { + const bodyPromise = clone.blob(); + bodyPromise.then( + function( body ) { + const request = event.request; + const req = new Request( request.url, { + method: request.method, + headers: request.headers, + mode: 'same-origin', + credentials: request.credentials, + referrer: request.referrer, + redirect: 'manual', + body, + } ); + + // Add request to queue. + queue.pushRequest( { + request: req, + } ); + } + ); + + const init = { + status: null, + }; + + return new Response( JSON.stringify( { error: 'test' } ), init ); + } ); + }; + + wp.serviceWorker.routing.registerRoute( + /\/index\.php/, + editPostHandler, + 'POST' + ); +} )(); diff --git a/lib/offline-editing.php b/lib/offline-editing.php new file mode 100644 index 0000000000000..a7030475c8a59 --- /dev/null +++ b/lib/offline-editing.php @@ -0,0 +1,17 @@ +register( + 'gutenberg-offline-editing', + array( + 'src' => 'https://pwa.wordpress.test/wp-content/plugins/gutenberg/lib/offline-editing.js', + 'deps' => array( 'wp-base-config' ), + ) + ); +} + +add_action( 'wp_admin_service_worker', 'gutenberg_register_offline_editing_service_worker_script' ); diff --git a/packages/api-fetch/src/index.js b/packages/api-fetch/src/index.js index 535adb8d88a76..be122dff89a46 100644 --- a/packages/api-fetch/src/index.js +++ b/packages/api-fetch/src/index.js @@ -92,10 +92,10 @@ const defaultFetchHandler = ( nextOptions ) => { .then( checkStatus ) .catch( ( response ) => parseAndThrowError( response, parse ) ) .then( ( response ) => parseResponseAndNormalizeError( response, parse ) ), - () => { + ( test ) => { + debugger; throw { - code: 'fetch_error', - message: __( 'You are probably offline.' ), + message: __( 'You do not have an internet connection, however, your changes will be synced once back online. Previewing and some editing is not available when offline.' ), }; } ); diff --git a/packages/api-fetch/src/utils/response.js b/packages/api-fetch/src/utils/response.js index bb7b79ce2f825..bf121ac43750e 100644 --- a/packages/api-fetch/src/utils/response.js +++ b/packages/api-fetch/src/utils/response.js @@ -59,6 +59,10 @@ export function parseAndThrowError( response, shouldParseResponse = true ) { return parseJsonAndNormalizeError( response ) .then( ( error ) => { + if ( 'test' === error.error ) { + debugger; + return response; + } const unknownError = { code: 'unknown_error', message: __( 'An unknown error occurred.' ), diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 82a91c3617a4d..79d2825f5d97b 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -22,6 +22,7 @@ import { getNotificationArgumentsForSaveSuccess, getNotificationArgumentsForSaveFail, getNotificationArgumentsForTrashFail, + getNotificationArgumentsForOfflineSync, } from './utils/notice-builder'; import serializeBlocks from './utils/serialize-blocks'; @@ -261,7 +262,8 @@ export function* savePost( options = {} ) { previousRecord.type, previousRecord.id ); - if ( error ) { + debugger; + if ( error && error.message === 'test' ) { const args = getNotificationArgumentsForSaveFail( { post: previousRecord, edits, @@ -270,6 +272,18 @@ export function* savePost( options = {} ) { if ( args.length ) { yield dispatch( 'core/notices', 'createErrorNotice', ...args ); } + } else if ( error ) { + const args = getNotificationArgumentsForOfflineSync( { + post: previousRecord, + edits, + error, + } ); + if ( args.length ) { + yield dispatch( 'core/notices', 'createWarningNotice', ...args ); + } + if ( ! options.isAutosave ) { + yield dispatch( 'core/block-editor', '__unstableMarkLastChangeAsPersistent' ); + } } else { const updatedRecord = yield select( STORE_KEY, 'getCurrentPost' ); const args = getNotificationArgumentsForSaveSuccess( { diff --git a/packages/editor/src/store/utils/notice-builder.js b/packages/editor/src/store/utils/notice-builder.js index 32ba7dacca5c2..382d3be931976 100644 --- a/packages/editor/src/store/utils/notice-builder.js +++ b/packages/editor/src/store/utils/notice-builder.js @@ -13,6 +13,20 @@ import { SAVE_POST_NOTICE_ID, TRASH_POST_NOTICE_ID } from '../constants'; */ import { get, includes } from 'lodash'; +export function getNotificationArgumentsForOfflineSync( data ) { + const { error } = data; + let noticeMessage = ''; + + // Check if message string contains HTML. Notice text is currently only + // supported as plaintext, and stripping the tags may muddle the meaning. + if ( error.message && ! ( /<\/?[^>]*>/.test( error.message ) ) ) { + noticeMessage = [ error.message ].join( ' ' ); + } + return [ noticeMessage, { + id: SAVE_POST_NOTICE_ID, + } ]; +} + /** * Builds the arguments for a success notification dispatch. * From 1937a98e741c590e5a1c301520e8c5a1ef2c47c4 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Thu, 2 Jan 2020 16:22:49 +0100 Subject: [PATCH 2/4] Remove some of the debugging information. --- lib/offline-editing.js | 7 ------- packages/api-fetch/src/index.js | 4 ++-- packages/core-data/src/actions.js | 2 +- packages/editor/src/store/actions.js | 18 +++++++++--------- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/lib/offline-editing.js b/lib/offline-editing.js index 305bbd1a31177..3d8e1e84b1c35 100644 --- a/lib/offline-editing.js +++ b/lib/offline-editing.js @@ -3,7 +3,6 @@ // IIFE is used for lexical scoping instead of just a braces block due to bug with const in Safari. ( () => { const queue = new wp.serviceWorker.backgroundSync.Queue( 'gutenbergPendingEdits' ); - const errorMessages = {}; const editPostHandler = ( { url, event } ) => { const clone = event.request.clone(); @@ -32,12 +31,6 @@ } ); } ); - - const init = { - status: null, - }; - - return new Response( JSON.stringify( { error: 'test' } ), init ); } ); }; diff --git a/packages/api-fetch/src/index.js b/packages/api-fetch/src/index.js index be122dff89a46..66252ef843dd6 100644 --- a/packages/api-fetch/src/index.js +++ b/packages/api-fetch/src/index.js @@ -92,9 +92,9 @@ const defaultFetchHandler = ( nextOptions ) => { .then( checkStatus ) .catch( ( response ) => parseAndThrowError( response, parse ) ) .then( ( response ) => parseResponseAndNormalizeError( response, parse ) ), - ( test ) => { - debugger; + () => { throw { + type: 'offline', message: __( 'You do not have an internet connection, however, your changes will be synced once back online. Previewing and some editing is not available when offline.' ), }; } diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 66fbd292ec340..d9cea321f0edf 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -366,7 +366,7 @@ export function* saveEntityRecord( // If we got to the point in the try block where we made an optimistic update, // we need to roll it back here. - if ( persistedEntity && currentEdits ) { + if ( 'offline' !== error.type && persistedEntity && currentEdits ) { yield receiveEntityRecords( kind, name, persistedEntity, undefined, true ); yield editEntityRecord( kind, diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 79d2825f5d97b..391b20b0af7bb 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -262,27 +262,27 @@ export function* savePost( options = {} ) { previousRecord.type, previousRecord.id ); - debugger; - if ( error && error.message === 'test' ) { - const args = getNotificationArgumentsForSaveFail( { + + if ( error && 'offline' === error.type ) { + const args = getNotificationArgumentsForOfflineSync( { post: previousRecord, edits, error, } ); if ( args.length ) { - yield dispatch( 'core/notices', 'createErrorNotice', ...args ); + yield dispatch( 'core/notices', 'createWarningNotice', ...args ); + } + if ( ! options.isAutosave ) { + yield dispatch( 'core/block-editor', '__unstableMarkLastChangeAsPersistent' ); } } else if ( error ) { - const args = getNotificationArgumentsForOfflineSync( { + const args = getNotificationArgumentsForSaveFail( { post: previousRecord, edits, error, } ); if ( args.length ) { - yield dispatch( 'core/notices', 'createWarningNotice', ...args ); - } - if ( ! options.isAutosave ) { - yield dispatch( 'core/block-editor', '__unstableMarkLastChangeAsPersistent' ); + yield dispatch( 'core/notices', 'createErrorNotice', ...args ); } } else { const updatedRecord = yield select( STORE_KEY, 'getCurrentPost' ); From 85b8793c066a329ff27ce3ae50422614bc782cea Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Thu, 2 Jan 2020 18:02:35 +0100 Subject: [PATCH 3/4] Make service worker url dynamic. --- lib/offline-editing.js | 1 + lib/offline-editing.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/offline-editing.js b/lib/offline-editing.js index 3d8e1e84b1c35..1340c83047b07 100644 --- a/lib/offline-editing.js +++ b/lib/offline-editing.js @@ -1,3 +1,4 @@ +/* @todo This file should be moved to a better location. */ /* global wp, fetch, caches, ERROR_OFFLINE_URL, ERROR_MESSAGES, ERROR_500_URL */ // IIFE is used for lexical scoping instead of just a braces block due to bug with const in Safari. diff --git a/lib/offline-editing.php b/lib/offline-editing.php index a7030475c8a59..fb8d17ab66665 100644 --- a/lib/offline-editing.php +++ b/lib/offline-editing.php @@ -8,7 +8,7 @@ function gutenberg_register_offline_editing_service_worker_script( $scripts ) { $scripts->register( 'gutenberg-offline-editing', array( - 'src' => 'https://pwa.wordpress.test/wp-content/plugins/gutenberg/lib/offline-editing.js', + 'src' => plugin_dir_url( __FILE__ ) . 'offline-editing.js', 'deps' => array( 'wp-base-config' ), ) ); From 200228e3b6a72cffac99d4e52ca7d644eddfd9c1 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Wed, 8 Jan 2020 20:52:36 +0100 Subject: [PATCH 4/4] Remove debugging lines. --- packages/api-fetch/src/utils/response.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/api-fetch/src/utils/response.js b/packages/api-fetch/src/utils/response.js index bf121ac43750e..bb7b79ce2f825 100644 --- a/packages/api-fetch/src/utils/response.js +++ b/packages/api-fetch/src/utils/response.js @@ -59,10 +59,6 @@ export function parseAndThrowError( response, shouldParseResponse = true ) { return parseJsonAndNormalizeError( response ) .then( ( error ) => { - if ( 'test' === error.error ) { - debugger; - return response; - } const unknownError = { code: 'unknown_error', message: __( 'An unknown error occurred.' ),