]*?>/gi,HTMLcommentRegExp://g,spaceRegExp:/ | /gi,HTMLEntityRegExp:/&\S+?;/g,connectorRegExp:/--|\u2014/g,removeRegExp:new RegExp(["[","!-@[-`{-~","-¿×÷"," -⯿","⸀-","]"].join(""),"g"),astralRegExp:/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,wordsRegExp:/\S\s+/g,characters_excluding_spacesRegExp:/\S/g,characters_including_spacesRegExp:/[^\f\n\r\t\v\u00AD\u2028\u2029]/g,l10n:{type:"words"}};function i(e,t){if(e.HTMLRegExp)return t.replace(e.HTMLRegExp,"\n")}function c(e,t){return e.astralRegExp?t.replace(e.astralRegExp,"a"):t}function u(e,t){return e.HTMLEntityRegExp?t.replace(e.HTMLEntityRegExp,""):t}function p(e,t){return e.connectorRegExp?t.replace(e.connectorRegExp," "):t}function s(e,t){return e.removeRegExp?t.replace(e.removeRegExp,""):t}function a(e,t){return e.HTMLcommentRegExp?t.replace(e.HTMLcommentRegExp,""):t}function g(e,t){return e.shortcodesRegExp?t.replace(e.shortcodesRegExp,"\n"):t}function d(e,t){if(e.spaceRegExp)return t.replace(e.spaceRegExp," ")}function f(e,t){return e.HTMLEntityRegExp?t.replace(e.HTMLEntityRegExp,"a"):t}function l(e,t,n){if(""===e)return 0;if(e){var l=function(e,t){var n=Object(r.extend)(o,t);return n.shortcodes=n.l10n.shortcodes||{},n.shortcodes&&n.shortcodes.length&&(n.shortcodesRegExp=new RegExp("\\[\\/?(?:"+n.shortcodes.join("|")+")[^\\]]*?\\]","g")),n.type=e||n.l10n.type,"characters_excluding_spaces"!==n.type&&"characters_including_spaces"!==n.type&&(n.type="words"),n}(t,n),x=l[t+"RegExp"],E="words"===l.type?function(e,t,n){return e=Object(r.flow)(i.bind(this,n),a.bind(this,n),g.bind(this,n),d.bind(this,n),u.bind(this,n),p.bind(this,n),s.bind(this,n))(e),(e+="\n").match(t)}(e,x,l):function(e,t,n){return e=Object(r.flow)(i.bind(this,n),a.bind(this,n),g.bind(this,n),d.bind(this,n),c.bind(this,n),f.bind(this,n))(e),(e+="\n").match(t)}(e,x,l);return E?E.length:0}}}});
--------------------------------------------------------------------------------
/public/vendor/no-conflict.js:
--------------------------------------------------------------------------------
1 | window.lodash = window._.noConflict();
2 |
--------------------------------------------------------------------------------
/scripts/g-scripts.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const runner = require('child_process');
5 |
6 | // const file = './public/vendor/gutenberg/wp-script-loader-packages.php';
7 |
8 | // TODO: check if it's a file.
9 | const file = './tmp/WordPress/wp-includes/assets/script-loader-packages.php';
10 |
11 | runner.exec('php -r \'print json_encode(include("' + file + '"));\'', function(err, stdout, stderr) {
12 | const json = JSON.stringify(JSON.parse(stdout));
13 | fs.writeFile('./config/gutenberg/scripts.json', json, (err) => {
14 | // In case of a error throw err.
15 | if (err) {
16 | console.log(err);
17 | throw err;
18 | }
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/scripts/g-update.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs')
4 |
5 | // Get updated list of dependencies
6 | const packages = require('../config/gutenberg')();
7 |
8 | const scripts = packages.scripts.reduce((html, script) => {
9 | if (!script.path) {
10 | return html;
11 | }
12 |
13 | return html + '';
14 | }, '');
15 |
16 | const styles = packages.styles.reduce((html, style) => {
17 | if (!style.path) {
18 | return html;
19 | }
20 |
21 | return html + ' ';
22 | }, '');
23 |
24 | fs.writeFile('g-scripts.txt', scripts, (err) => {
25 | // In case of a error throw err.
26 | if (err) {
27 | console.log(err);
28 | throw err;
29 | }
30 | });
31 |
32 | fs.writeFile('g-styles.txt', styles, (err) => {
33 | // In case of a error throw err.
34 | if (err) {
35 | console.log(err);
36 | throw err;
37 | }
38 | });
39 |
--------------------------------------------------------------------------------
/scripts/start.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'development';
5 | process.env.NODE_ENV = 'development';
6 |
7 | // Makes the script crash on unhandled rejections instead of silently
8 | // ignoring them. In the future, promise rejections that are not handled will
9 | // terminate the Node.js process with a non-zero exit code.
10 | process.on('unhandledRejection', err => {
11 | throw err;
12 | });
13 |
14 | // Ensure environment variables are read.
15 | require('../config/env');
16 |
17 |
18 | const fs = require('fs');
19 | const chalk = require('chalk');
20 | const webpack = require('webpack');
21 | const WebpackDevServer = require('webpack-dev-server');
22 | const clearConsole = require('react-dev-utils/clearConsole');
23 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
24 | const {
25 | choosePort,
26 | createCompiler,
27 | prepareProxy,
28 | prepareUrls,
29 | } = require('react-dev-utils/WebpackDevServerUtils');
30 | const openBrowser = require('react-dev-utils/openBrowser');
31 | const paths = require('../config/paths');
32 | const config = require('../config/webpack.config.dev');
33 | const createDevServerConfig = require('../config/webpackDevServer.config');
34 |
35 | const useYarn = fs.existsSync(paths.yarnLockFile);
36 | const isInteractive = process.stdout.isTTY;
37 |
38 | // Warn and crash if required files are missing
39 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
40 | process.exit(1);
41 | }
42 |
43 | // Tools like Cloud9 rely on this.
44 | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
45 | const HOST = process.env.HOST || '0.0.0.0';
46 |
47 | if (process.env.HOST) {
48 | console.log(
49 | chalk.cyan(
50 | `Attempting to bind to HOST environment variable: ${chalk.yellow(
51 | chalk.bold(process.env.HOST)
52 | )}`
53 | )
54 | );
55 | console.log(
56 | `If this was unintentional, check that you haven't mistakenly set it in your shell.`
57 | );
58 | console.log(
59 | `Learn more here: ${chalk.yellow('http://bit.ly/CRA-advanced-config')}`
60 | );
61 | console.log();
62 | }
63 |
64 | // We require that you explictly set browsers and do not fall back to
65 | // browserslist defaults.
66 | const { checkBrowsers } = require('react-dev-utils/browsersHelper');
67 | checkBrowsers(paths.appPath, isInteractive)
68 | .then(() => {
69 | // We attempt to use the default port but if it is busy, we offer the user to
70 | // run on a different port. `choosePort()` Promise resolves to the next free port.
71 | return choosePort(HOST, DEFAULT_PORT);
72 | })
73 | .then(port => {
74 | if (port == null) {
75 | // We have not found a port.
76 | return;
77 | }
78 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
79 | const appName = require(paths.appPackageJson).name;
80 | const urls = prepareUrls(protocol, HOST, port);
81 | // Create a webpack compiler that is configured with custom messages.
82 | const compiler = createCompiler(webpack, config, appName, urls, useYarn);
83 | // Load proxy config
84 | const proxySetting = require(paths.appPackageJson).proxy;
85 | const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
86 | // Serve webpack assets generated by the compiler over a web server.
87 | const serverConfig = createDevServerConfig(
88 | proxyConfig,
89 | urls.lanUrlForConfig
90 | );
91 | const devServer = new WebpackDevServer(compiler, serverConfig);
92 | // Launch WebpackDevServer.
93 | devServer.listen(port, HOST, err => {
94 | if (err) {
95 | return console.log(err);
96 | }
97 | if (isInteractive) {
98 | clearConsole();
99 | }
100 | console.log(chalk.cyan('Starting the development server...\n'));
101 | openBrowser(urls.localUrlForBrowser);
102 | });
103 |
104 | ['SIGINT', 'SIGTERM'].forEach(function(sig) {
105 | process.on(sig, function() {
106 | devServer.close();
107 | process.exit();
108 | });
109 | });
110 | })
111 | .catch(err => {
112 | if (err && err.message) {
113 | console.log(err.message);
114 | }
115 | process.exit(1);
116 | });
117 |
--------------------------------------------------------------------------------
/scripts/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'test';
5 | process.env.NODE_ENV = 'test';
6 | process.env.PUBLIC_URL = '';
7 |
8 | // Makes the script crash on unhandled rejections instead of silently
9 | // ignoring them. In the future, promise rejections that are not handled will
10 | // terminate the Node.js process with a non-zero exit code.
11 | process.on('unhandledRejection', err => {
12 | throw err;
13 | });
14 |
15 | // Ensure environment variables are read.
16 | require('../config/env');
17 |
18 |
19 | const jest = require('jest');
20 | const execSync = require('child_process').execSync;
21 | let argv = process.argv.slice(2);
22 |
23 | function isInGitRepository() {
24 | try {
25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
26 | return true;
27 | } catch (e) {
28 | return false;
29 | }
30 | }
31 |
32 | function isInMercurialRepository() {
33 | try {
34 | execSync('hg --cwd . root', { stdio: 'ignore' });
35 | return true;
36 | } catch (e) {
37 | return false;
38 | }
39 | }
40 |
41 | // Watch unless on CI, in coverage mode, or explicitly running all tests
42 | if (
43 | !process.env.CI &&
44 | argv.indexOf('--coverage') === -1 &&
45 | argv.indexOf('--watchAll') === -1
46 | ) {
47 | // https://github.com/facebook/create-react-app/issues/5210
48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository();
49 | argv.push(hasSourceControl ? '--watch' : '--watchAll');
50 | }
51 |
52 |
53 | jest.run(argv);
54 |
--------------------------------------------------------------------------------
/src/core/api-fetch.js:
--------------------------------------------------------------------------------
1 |
2 | import apiFetch from '../globals/api-fetch';
3 |
4 | const {
5 | use,
6 | createNonceMiddleware,
7 | createRootURLMiddleware,
8 | setFetchHandler,
9 | } = window.wp.apiFetch;
10 |
11 | const nonceMiddleware = createNonceMiddleware(window.wpApiSettings.nonce);
12 |
13 | use(nonceMiddleware);
14 |
15 | window.wp.hooks.addAction(
16 | 'heartbeat.tick',
17 | 'core/api-fetch/create-nonce-middleware',
18 | function (response) {
19 | if (response.rest_nonce) {
20 | nonceMiddleware.nonce = response.rest_nonce;
21 | }
22 | }
23 | );
24 |
25 | use(createRootURLMiddleware(window.wpApiSettings.root));
26 |
27 | setFetchHandler(apiFetch);
28 |
--------------------------------------------------------------------------------
/src/core/index.js:
--------------------------------------------------------------------------------
1 |
2 | import './settings';
3 | import './api-fetch';
4 |
5 | import './media-upload';
6 |
7 | import './style.scss';
8 |
--------------------------------------------------------------------------------
/src/core/media-library.scss:
--------------------------------------------------------------------------------
1 |
2 | .media-library__popover {
3 | > div {
4 | padding: 1em;
5 | }
6 |
7 | .media-library__popover__content {
8 | max-height: 465px;
9 | display: grid;
10 | grid-template-columns: repeat(5, auto);
11 | grid-gap: 1em;
12 |
13 | .media-library-thumbnail {
14 | width: 100px;
15 | height: 100px;
16 | border: 1px solid #e2e4e7;
17 | padding: 5px;
18 | background-position: center;
19 | background-size: contain;
20 | background-repeat: no-repeat;
21 | background-color: hsla(240,5%,57%,.1);
22 |
23 | &:hover {
24 | cursor: pointer;
25 | box-shadow: inset 0 0 0 1px rgba(25,30,35,.2), 0 1px 3px rgba(25,30,35,.4);
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/core/media-upload.js:
--------------------------------------------------------------------------------
1 |
2 | import { Component, Fragment } from 'react';
3 | import './media-library.scss';
4 |
5 | const { wp, lodash } = window;
6 | const { get } = lodash;
7 |
8 | const { __ } = wp.i18n;
9 | const { Popover } = wp.components;
10 | const { withSelect } = wp.data;
11 | const { addFilter } = wp.hooks;
12 |
13 |
14 | class MediaContainer extends Component {
15 | constructor (props) {
16 | super(props);
17 | this.onImageClick = this.onImageClick.bind(this);
18 | }
19 |
20 | onImageClick (img) {
21 | const { onSelect, closePopover, gallery = false, multiple = false } = this.props;
22 |
23 | const imgObject = {
24 | alt: img.alt_text,
25 | caption: img.caption.raw,
26 | id: img.id,
27 | link: img.link,
28 | mime: img.mime_type,
29 | sizes: img.media_details.sizes,
30 | media_details: img.media_details,
31 | subtype: img.mime_type.split('/')[ 1 ],
32 | type: img.mime_type.split('/')[ 0 ],
33 | url: img.source_url,
34 | data: img.data,
35 | };
36 |
37 | if (gallery || multiple) {
38 | onSelect([imgObject]);
39 | }
40 | else {
41 | onSelect(imgObject);
42 | }
43 | closePopover();
44 | }
45 |
46 | render () {
47 | const { media, allowedTypes = [] } = this.props;
48 | const items = media && media.filter(item => ! allowedTypes.length || allowedTypes.includes(item.media_type));
49 | return (
50 |
51 | { items && items.map(item => {
52 | const sourceUrl = get(item, 'media_details.sizes.thumbnail.source_url') ||
53 | (item.media_type === 'image' && item.source_url);
54 | const buttonStyle = sourceUrl ? { backgroundImage: `url(${sourceUrl})` } : {};
55 |
56 | return this.onImageClick(item) }
61 | > ;
62 | }) }
63 |
64 | );
65 | }
66 | }
67 |
68 |
69 | const MediaLibrary = withSelect(select => ({
70 | media: select('core').getMediaItems(),
71 | }))(MediaContainer);
72 |
73 |
74 | class MediaUpload extends Component {
75 | constructor (props) {
76 | super(props);
77 | this.state = { isVisible: false };
78 |
79 | this.openPopover = this.openPopover.bind(this);
80 | this.closePopover = this.closePopover.bind(this);
81 | }
82 |
83 | openPopover () {
84 | this.setState({ isVisible: true });
85 | }
86 |
87 | closePopover () {
88 | this.setState({ isVisible: false });
89 | }
90 |
91 | render () {
92 | if (!this.props.mediaLibrary) {
93 | console.log('Media Library is deactivated');
94 | return false;
95 | }
96 |
97 | const { isVisible } = this.state;
98 |
99 | return
100 | { isVisible &&
101 | event.stopPropagation() }
105 | position="middle left"
106 | headerTitle={ __('Media Library') }
107 | >
108 |
112 |
113 | }
114 | { this.props.render({ open: this.openPopover }) }
115 | ;
116 | }
117 | }
118 |
119 |
120 | const replaceMediaUpload = () => withSelect(select => ({
121 | mediaLibrary: select('core/editor').getEditorSettings().mediaLibrary,
122 | }))(MediaUpload);
123 |
124 |
125 | addFilter(
126 | 'editor.MediaUpload',
127 | 'core/edit-post/components/media-upload/replace-media-upload',
128 | replaceMediaUpload,
129 | );
130 |
--------------------------------------------------------------------------------
/src/core/settings.js:
--------------------------------------------------------------------------------
1 |
2 | // User settings
3 | window.userSettings = {
4 | secure: '',
5 | time: 1234567,
6 | uid: 1,
7 | };
8 |
9 | // API settings
10 | window.wpApiSettings = {
11 | root: window.location.origin + '/',
12 | nonce: '123456789',
13 | versionString: 'wp/v2/',
14 | };
15 |
16 | // postboxes
17 | window.postboxes = window.postboxes || {
18 | add_postbox_toggles: (page, args) => {
19 | console.log('page', page);
20 | console.log('args', args);
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/src/data/categories.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 2,
4 | "count": 3,
5 | "description": "Neque quibusdam nihil sequi quia et inventore dolorem dolores et consequuntur nostrum delectus esse cum et voluptatem ut rerum et accusamus quae vel neque laudantium optio rerum asperiores assumenda rerum qui eius neque dolores id quibusdam id optio ut eius dolor qui quas non",
6 | "link": "https://demo.wp-api.org/category/aut-architecto-nihil/",
7 | "name": "Aut architecto nihil",
8 | "slug": "aut-architecto-nihil",
9 | "taxonomy": "category",
10 | "parent": 0,
11 | "meta": [],
12 | "_links": {
13 | "self": [
14 | {
15 | "href": "https://demo.wp-api.org/wp-json/wp/v2/categories/2"
16 | }
17 | ],
18 | "collection": [
19 | {
20 | "href": "https://demo.wp-api.org/wp-json/wp/v2/categories"
21 | }
22 | ],
23 | "about": [
24 | {
25 | "href": "https://demo.wp-api.org/wp-json/wp/v2/taxonomies/category"
26 | }
27 | ],
28 | "wp:post_type": [
29 | {
30 | "href": "https://demo.wp-api.org/wp-json/wp/v2/posts?categories=2"
31 | }
32 | ],
33 | "curies": [
34 | {
35 | "name": "wp",
36 | "href": "https://api.w.org/{rel}",
37 | "templated": true
38 | }
39 | ]
40 | }
41 | },
42 | {
43 | "id": 11,
44 | "count": 7,
45 | "description": "Rem recusandae velit et incidunt labore qui explicabo veritatis eos quod dolor dolor occaecati nobis in suscipit et quo impedit repellat eius voluptatem",
46 | "link": "https://demo.wp-api.org/category/facilis-dignissimos/",
47 | "name": "Facilis dignissimos",
48 | "slug": "facilis-dignissimos",
49 | "taxonomy": "category",
50 | "parent": 0,
51 | "meta": [],
52 | "_links": {
53 | "self": [
54 | {
55 | "href": "https://demo.wp-api.org/wp-json/wp/v2/categories/11"
56 | }
57 | ],
58 | "collection": [
59 | {
60 | "href": "https://demo.wp-api.org/wp-json/wp/v2/categories"
61 | }
62 | ],
63 | "about": [
64 | {
65 | "href": "https://demo.wp-api.org/wp-json/wp/v2/taxonomies/category"
66 | }
67 | ],
68 | "wp:post_type": [
69 | {
70 | "href": "https://demo.wp-api.org/wp-json/wp/v2/posts?categories=11"
71 | }
72 | ],
73 | "curies": [
74 | {
75 | "name": "wp",
76 | "href": "https://api.w.org/{rel}",
77 | "templated": true
78 | }
79 | ]
80 | }
81 | },
82 | {
83 | "id": 1,
84 | "count": 5,
85 | "description": "",
86 | "link": "https://demo.wp-api.org/category/uncategorized/",
87 | "name": "Uncategorized",
88 | "slug": "uncategorized",
89 | "taxonomy": "category",
90 | "parent": 0,
91 | "meta": [],
92 | "_links": {
93 | "self": [
94 | {
95 | "href": "https://demo.wp-api.org/wp-json/wp/v2/categories/1"
96 | }
97 | ],
98 | "collection": [
99 | {
100 | "href": "https://demo.wp-api.org/wp-json/wp/v2/categories"
101 | }
102 | ],
103 | "about": [
104 | {
105 | "href": "https://demo.wp-api.org/wp-json/wp/v2/taxonomies/category"
106 | }
107 | ],
108 | "wp:post_type": [
109 | {
110 | "href": "https://demo.wp-api.org/wp-json/wp/v2/posts?categories=1"
111 | }
112 | ],
113 | "curies": [
114 | {
115 | "name": "wp",
116 | "href": "https://api.w.org/{rel}",
117 | "templated": true
118 | }
119 | ]
120 | }
121 | }
122 | ]
123 |
--------------------------------------------------------------------------------
/src/data/taxonomies.json:
--------------------------------------------------------------------------------
1 | {
2 | "category": {
3 | "name": "Categories",
4 | "slug": "category",
5 | "description": "",
6 | "types": [
7 | "post"
8 | ],
9 | "visibility": { "show_ui": false },
10 | "hierarchical": true,
11 | "rest_base": "categories",
12 | "_links": {
13 | "collection": [
14 | {
15 | "href": "https://demo.wp-api.org/wp-json/wp/v2/taxonomies"
16 | }
17 | ],
18 | "wp:items": [
19 | {
20 | "href": "https://demo.wp-api.org/wp-json/wp/v2/categories"
21 | }
22 | ],
23 | "curies": [
24 | {
25 | "name": "wp",
26 | "href": "https://api.w.org/{rel}",
27 | "templated": true
28 | }
29 | ]
30 | },
31 | "labels": {
32 | "singular_name": "Category"
33 | }
34 | },
35 | "post_tag": {
36 | "name": "Tags",
37 | "slug": "post_tag",
38 | "description": "",
39 | "types": [
40 | "post"
41 | ],
42 | "visibility": { "show_ui": false },
43 | "hierarchical": false,
44 | "rest_base": "tags",
45 | "_links": {
46 | "collection": [
47 | {
48 | "href": "https://demo.wp-api.org/wp-json/wp/v2/taxonomies"
49 | }
50 | ],
51 | "wp:items": [
52 | {
53 | "href": "https://demo.wp-api.org/wp-json/wp/v2/tags"
54 | }
55 | ],
56 | "curies": [
57 | {
58 | "name": "wp",
59 | "href": "https://api.w.org/{rel}",
60 | "templated": true
61 | }
62 | ]
63 | },
64 | "labels": {
65 | "singular_name": "Tag"
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/data/themes.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "theme_supports": {
4 | "formats": [
5 | "standard",
6 | "aside",
7 | "image",
8 | "video",
9 | "quote",
10 | "link",
11 | "gallery",
12 | "audio"
13 | ],
14 | "post-thumbnails": true,
15 | "responsive-embeds": false
16 | }
17 | }
18 | ]
19 |
--------------------------------------------------------------------------------
/src/data/types.json:
--------------------------------------------------------------------------------
1 | {
2 | "post": {
3 | "description": "",
4 | "hierarchical": false,
5 | "viewable": true,
6 | "name": "Posts",
7 | "slug": "post",
8 | "supports": {
9 | "author": false,
10 | "comments": false,
11 | "custom-fields": true,
12 | "editor": true,
13 | "excerpt": false,
14 | "page-attributes": false,
15 | "revisions": false,
16 | "thumbnail": false,
17 | "title": true,
18 | "post-formats": true
19 | },
20 | "taxonomies": [
21 | "category",
22 | "post_tag"
23 | ],
24 | "rest_base": "posts",
25 | "_links": {
26 | "collection": [
27 | {
28 | "href": "https://demo.wp-api.org/wp-json/wp/v2/types"
29 | }
30 | ],
31 | "wp:items": [
32 | {
33 | "href": "https://demo.wp-api.org/wp-json/wp/v2/posts"
34 | }
35 | ],
36 | "curies": [
37 | {
38 | "name": "wp",
39 | "href": "https://api.w.org/{rel}",
40 | "templated": true
41 | }
42 | ]
43 | },
44 | "labels": {
45 | "singular_name": "Post"
46 | }
47 | },
48 | "page": {
49 | "description": "",
50 | "hierarchical": true,
51 | "viewable": true,
52 | "name": "Pages",
53 | "slug": "page",
54 | "supports": {
55 | "author": false,
56 | "comments": false,
57 | "custom-fields": false,
58 | "discussion": false,
59 | "editor": true,
60 | "excerpt": true,
61 | "page-attributes": false,
62 | "revisions": false,
63 | "thumbnail": false,
64 | "title": false
65 | },
66 | "taxonomies": [],
67 | "rest_base": "pages",
68 | "_links": {
69 | "collection": [
70 | {
71 | "href": "https://demo.wp-api.org/wp-json/wp/v2/types"
72 | }
73 | ],
74 | "wp:items": [
75 | {
76 | "href": "https://demo.wp-api.org/wp-json/wp/v2/pages"
77 | }
78 | ],
79 | "curies": [
80 | {
81 | "name": "wp",
82 | "href": "https://api.w.org/{rel}",
83 | "templated": true
84 | }
85 | ]
86 | },
87 | "labels": {
88 | "singular_name": "Page"
89 | }
90 | },
91 | "attachment": {
92 | "description": "",
93 | "hierarchical": false,
94 | "viewable": true,
95 | "name": "Media",
96 | "slug": "attachment",
97 | "supports": {
98 | "title": true,
99 | "author": true,
100 | "comments": true
101 | },
102 | "taxonomies": [],
103 | "rest_base": "media",
104 | "_links": {
105 | "collection": [
106 | {
107 | "href": "https://demo.wp-api.org/wp-json/wp/v2/types"
108 | }
109 | ],
110 | "wp:items": [
111 | {
112 | "href": "https://demo.wp-api.org/wp-json/wp/v2/media"
113 | }
114 | ],
115 | "curies": [
116 | {
117 | "name": "wp",
118 | "href": "https://api.w.org/{rel}",
119 | "templated": true
120 | }
121 | ]
122 | },
123 | "labels": {
124 | "singular_name": "Media"
125 | }
126 | },
127 | "wp_block": {
128 | "description": "",
129 | "hierarchical": false,
130 | "viewable": false,
131 | "name": "Blocks",
132 | "slug": "wp_block",
133 | "supports": {
134 | "title": true,
135 | "editor": true
136 | },
137 | "taxonomies": [],
138 | "rest_base": "blocks",
139 | "_links": {
140 | "collection": [
141 | {
142 | "href": "https://demo.wp-api.org/wp-json/wp/v2/types"
143 | }
144 | ],
145 | "wp:items": [
146 | {
147 | "href": "https://demo.wp-api.org/wp-json/wp/v2/blocks"
148 | }
149 | ],
150 | "curies": [
151 | {
152 | "name": "wp",
153 | "href": "https://api.w.org/{rel}",
154 | "templated": true
155 | }
156 | ]
157 | },
158 | "headers": [],
159 | "labels": {
160 | "singular_name": "Block"
161 | }
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/src/data/users.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 1,
4 | "name": "Human Made",
5 | "url": "",
6 | "description": "",
7 | "link": "https://demo.wp-api.org/author/humanmade/",
8 | "slug": "humanmade",
9 | "avatar_urls": {
10 | "24": "https://secure.gravatar.com/avatar/83888eb8aea456e4322577f96b4dbaab?s=24&d=mm&r=g",
11 | "48": "https://secure.gravatar.com/avatar/83888eb8aea456e4322577f96b4dbaab?s=48&d=mm&r=g",
12 | "96": "https://secure.gravatar.com/avatar/83888eb8aea456e4322577f96b4dbaab?s=96&d=mm&r=g"
13 | },
14 | "meta": [],
15 | "_links": {
16 | "self": [
17 | {
18 | "href": "https://demo.wp-api.org/wp-json/wp/v2/users/1"
19 | }
20 | ],
21 | "collection": [
22 | {
23 | "href": "https://demo.wp-api.org/wp-json/wp/v2/users"
24 | }
25 | ]
26 | }
27 | }
28 | ]
29 |
--------------------------------------------------------------------------------
/src/globals/api-fetch.js:
--------------------------------------------------------------------------------
1 |
2 | import routes from './api-routes';
3 |
4 |
5 | function matchRoute (pattern, pathname) {
6 | const res = {};
7 | const r = pattern.split('/'), p = pathname.split('/');
8 | const l = Math.max(r.length, p.length);
9 |
10 | let i = 0;
11 | for(; i < l; i++) {
12 | if(r[i] === p[i]) {
13 | continue;
14 | }
15 | if(!r[i] || !p[i]) {
16 | return false;
17 | }
18 | if(r[i].charAt(0) === '{' && r[i].charAt(r[i].length - 1) === '}') {
19 | const param = r[i].slice(1, -1);
20 | if(param.includes('*')) {
21 | res[param.replace('*', '')] = p.slice(i).join('/');
22 | return res;
23 | }
24 | res[param] = p[i];
25 | continue;
26 | }
27 | return false;
28 | }
29 | return res;
30 | }
31 |
32 | function matchRouteList (options, pathname) {
33 | if(typeof options === 'string') {
34 | return matchRoute(options, pathname);
35 | }
36 |
37 | for(const pattern of options) {
38 | const params = matchRoute(pattern, pathname);
39 | if(params) {
40 | return params;
41 | }
42 | }
43 | return false;
44 | }
45 |
46 | function matchMethod (source, target) {
47 | return !target || target === '*' || target === source || target.includes(source);
48 | }
49 |
50 | function parseQS (qs) {
51 | return qs.split('&').reduce((a, i, p) => {
52 | p = i.split('=');
53 | a[decodeURIComponent(p[0])] = decodeURIComponent(p[1] || '');
54 | return a;
55 | }, {});
56 | }
57 |
58 |
59 | const apiFetch = options => {
60 | let { method = 'GET' } = options;
61 | const { path, data, body, url, headers } = options;
62 | const [ pathname, _qs ] = path.split('?');
63 | const query = parseQS(_qs);
64 | const payload = data || body;
65 |
66 | if(headers && headers['X-HTTP-Method-Override']) {
67 | method = headers['X-HTTP-Method-Override'];
68 | }
69 |
70 | // console.log(method, pathname);
71 |
72 | for(const r of routes) {
73 | const params = matchRouteList(r.path, pathname);
74 | if(params) {
75 | if(matchMethod(method, r.method)) {
76 | return r.handler({
77 | method, url, pathname, params, query, payload,
78 | });
79 | }
80 | }
81 | }
82 |
83 | console.error('Unmatched route:', method, path, payload);
84 | return {};
85 | };
86 |
87 | export default apiFetch;
88 |
--------------------------------------------------------------------------------
/src/globals/api-routes.js:
--------------------------------------------------------------------------------
1 |
2 | import { getPage, savePage, deletePage } from './fake-data.js';
3 | import { mediaList, createMedia } from './fake-media.js';
4 | import { getEmbed } from './embeds.js';
5 |
6 | import users from '../data/users';
7 | import taxonomies from '../data/taxonomies';
8 | import categories from '../data/categories';
9 | import types from '../data/types';
10 | import themes from '../data/themes';
11 |
12 |
13 | export default [
14 | // Pages and posts
15 | {
16 | path: '/wp/v2/pages',
17 | method: '*',
18 | handler () {
19 | return [ getPage() ];
20 | },
21 | },
22 | {
23 | path: [
24 | '/wp/v2/pages/{id}',
25 | '/wp/v2/pages/{id}/autosaves',
26 | ],
27 | method: ['POST', 'PUT'],
28 | handler ({ payload }) {
29 | savePage(payload);
30 | return getPage();
31 | },
32 | },
33 | {
34 | path: '/wp/v2/pages/{id}',
35 | method: 'DELETE',
36 | handler () {
37 | deletePage();
38 | return {};
39 | },
40 | },
41 | {
42 | path: [
43 | '/wp/v2/pages/{id}',
44 | '/wp/v2/pages/{id}/autosaves',
45 | ],
46 | method: '*',
47 | handler () {
48 | return getPage();
49 | },
50 | },
51 |
52 | {
53 | path: '/wp/v2/posts',
54 | method: '*',
55 | handler () {
56 | return [ getPage('post') ];
57 | },
58 | },
59 | {
60 | path: '/wp/v2/posts/{id}',
61 | method: 'DELETE',
62 | handler () {
63 | deletePage();
64 | return {};
65 | },
66 | },
67 | {
68 | path: [
69 | '/wp/v2/posts/{id}',
70 | '/wp/v2/posts/{id}/autosaves',
71 | ],
72 | method: ['POST', 'PUT'],
73 | handler ({ payload }) {
74 | savePage(payload);
75 | return getPage('post');
76 | },
77 | },
78 | {
79 | path: [
80 | '/wp/v2/posts/{id}',
81 | '/wp/v2/posts/{id}/autosaves',
82 | ],
83 | method: '*',
84 | handler () {
85 | return getPage('post');
86 | },
87 | },
88 |
89 | // Media
90 | {
91 | path: '/wp/v2/media',
92 | method: 'OPTIONS',
93 | handler () {
94 | return {
95 | headers: {
96 | get (value) {
97 | if (value === 'allow') { return [ 'POST' ]; }
98 | },
99 | },
100 | };
101 | },
102 | },
103 | {
104 | path: '/wp/v2/media',
105 | method: 'POST',
106 | async handler ({ payload }) {
107 | const file = payload.get('file');
108 | return file ? await createMedia(file) : {};
109 | },
110 | },
111 | {
112 | path: '/wp/v2/media',
113 | method: '*',
114 | handler () {
115 | return mediaList;
116 | },
117 | },
118 | {
119 | path: '/wp/v2/media/{id}',
120 | method: '*',
121 | handler ({ params }) {
122 | return mediaList[+params.id - 1];
123 | },
124 | },
125 |
126 |
127 | // Types
128 | {
129 | path: '/wp/v2/types',
130 | method: '*',
131 | handler () {
132 | return types;
133 | },
134 | },
135 | {
136 | path: '/wp/v2/types/{type}',
137 | method: '*',
138 | handler ({ params }) {
139 | return types[params.type] || {};
140 | },
141 | },
142 |
143 | // Blocks
144 | {
145 | path: '/wp/v2/blocks',
146 | method: '*',
147 | handler () {
148 | return [];
149 | },
150 | },
151 |
152 | // Themes
153 | {
154 | path: '/wp/v2/themes',
155 | method: '*',
156 | handler () {
157 | return themes;
158 | },
159 | },
160 |
161 | // Taxonomies
162 | {
163 | path: '/wp/v2/taxonomies',
164 | method: '*',
165 | handler () {
166 | return new window.Response(JSON.stringify(taxonomies));
167 | },
168 | },
169 | {
170 | path: '/wp/v2/taxonomies/{type}',
171 | method: '*',
172 | handler ({ params }) {
173 | return taxonomies[params.type] || {};
174 | },
175 | },
176 |
177 | // Categories
178 | {
179 | path: '/wp/v2/categories',
180 | method: '*',
181 | handler () {
182 | return categories;
183 | },
184 | },
185 |
186 | // Users
187 | {
188 | path: '/wp/v2/users/',
189 | method: '*',
190 | handler () {
191 | return new window.Response(JSON.stringify(users));
192 | },
193 | },
194 | {
195 | path: '/wp/v2/users/{name}',
196 | method: '*',
197 | handler () {
198 | return users[0] || {};
199 | },
200 | },
201 |
202 | // Block renderer
203 | {
204 | path: '/wp/v2/block-renderer/{block*}',
205 | method: '*',
206 | handler ({ params }) {
207 | return {
208 | rendered: `Sorry. There is no server-side rendering available for "${params.block}".
`,
209 | };
210 | },
211 | },
212 |
213 | // Search
214 | {
215 | path: '/wp/v2/search',
216 | method: '*',
217 | handler () {
218 | return [];
219 | },
220 | },
221 |
222 | // Embed
223 | {
224 | path: '/oembed/1.0/proxy',
225 | method: '*',
226 | handler ({ query }) {
227 | return getEmbed(query.url);
228 | },
229 | },
230 | ];
231 |
--------------------------------------------------------------------------------
/src/globals/embeds.js:
--------------------------------------------------------------------------------
1 | import providers from 'oembed-providers';
2 |
3 | export async function getEmbed (url) {
4 | // Proxy that we use that enables cross-origin requests
5 | // https://cors-anywhere.herokuapp.com/
6 | const cors_api_host = 'https://cors-anywhere.herokuapp.com/';
7 | const hostname = (new URL(url)).hostname;
8 |
9 | // Find the provider
10 | const provider = providers.find(({ provider_url }) => {
11 | return provider_url.includes(hostname) || hostname.includes(provider_url);
12 | });
13 |
14 | if(provider && provider.endpoints) {
15 | // Use the first endpoint
16 | const provider_url = provider.endpoints[0].url;
17 |
18 | // Fetch embed to provider through the proxy
19 | const response = await fetch(`${cors_api_host}${provider_url}?url=${url}`);
20 | return response.json();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/globals/fake-data.js:
--------------------------------------------------------------------------------
1 | const date = (new Date()).toISOString();
2 |
3 | export const pages = {
4 | page: {
5 | id: 1,
6 | content: {
7 | raw: '',
8 | rendered: '',
9 | },
10 | date,
11 | date_gmt: date,
12 | title: {
13 | raw: 'Preview page',
14 | rendered: 'Preview page',
15 | },
16 | excerpt: {
17 | raw: '',
18 | rendered: '',
19 | },
20 | status: 'draft',
21 | revisions: { count: 0, last_id: 0 },
22 | parent: 0,
23 | theme_style: true,
24 | type: 'page',
25 | link: `${window.location.origin}/preview`,
26 | categories: [ ],
27 | featured_media: 0,
28 | permalink_template: `${window.location.origin}/preview`,
29 | preview_link: `${window.location.origin}/preview`,
30 | _links: {
31 | 'wp:action-assign-categories': [],
32 | 'wp:action-create-categories': [],
33 | },
34 | },
35 | post: {
36 | id: 1,
37 | content: {
38 | raw: '',
39 | rendered: '',
40 | },
41 | date,
42 | date_gmt: date,
43 | title: {
44 | raw: 'Preview post',
45 | rendered: 'Preview post',
46 | },
47 | excerpt: {
48 | raw: '',
49 | rendered: '',
50 | },
51 | status: 'draft',
52 | revisions: { count: 0, last_id: 0 },
53 | parent: 0,
54 | theme_style: true,
55 | type: 'post',
56 | link: `${window.location.origin}/preview`,
57 | categories: [ ],
58 | featured_media: 0,
59 | permalink_template: `${window.location.origin}/preview`,
60 | preview_link: `${window.location.origin}/preview`,
61 | _links: {
62 | 'wp:action-assign-categories': [],
63 | 'wp:action-create-categories': [],
64 | },
65 | },
66 | };
67 |
68 |
69 | export function getPage (type = 'page') {
70 | return JSON.parse(localStorage.getItem('g-editor-page')) || pages[type];
71 | }
72 |
73 | export function savePage (data, type = 'page') {
74 | const item = {
75 | ...getPage(type),
76 | id: data.id || 1, // to prevent when id isn't passed as data (ex: autosaves)
77 | };
78 |
79 | if(data.title) {
80 | item.title = {
81 | raw: data.title,
82 | rendered: data.title,
83 | };
84 | }
85 | if(data.content) {
86 | item.content = {
87 | raw: data.content,
88 | // rendered: data.content.replace(/()/g, ''),
89 | };
90 | }
91 | if(data.excerpt) {
92 | item.excerpt = {
93 | raw: data.excerpt,
94 | rendered: data.excerpt,
95 | };
96 | }
97 |
98 | localStorage.setItem('g-editor-page', JSON.stringify(item));
99 | }
100 |
101 | export function changeType (type) {
102 | const item = getPage(type);
103 | item.type = type;
104 |
105 | localStorage.setItem('g-editor-page', JSON.stringify(item));
106 | }
107 |
108 | export function deletePage () {
109 | // Workaround to wait until the POST request (that is called after DELETE)
110 | // is finished.
111 | setTimeout(function () {
112 | localStorage.removeItem('g-editor-page');
113 | sessionStorage.removeItem('wp-autosave-block-editor-post-1');
114 | window.location.reload();
115 | }, 500);
116 | }
117 |
--------------------------------------------------------------------------------
/src/globals/fake-media.js:
--------------------------------------------------------------------------------
1 | const date = (new Date()).toISOString();
2 | const origin = window.location.origin;
3 |
4 | // List of images
5 | export const mediaList = [];
6 |
7 | export function getMedia (id, params = {}) {
8 | const sizes = {};
9 | if (params.thumbnail) {
10 | sizes.thumbnail = {
11 | source_url: params.thumbnail,
12 | };
13 | }
14 |
15 | return {
16 | id,
17 | title: { raw: '', rendered: '' },
18 | caption: { raw: '', rendered: '' },
19 | date_gmt: date,
20 | date,
21 | media_type: params.media_type,
22 | mime_type: params.mime_type,
23 | source_url: params.source_url,
24 | // link: params.source_url,
25 | media_details: {
26 | file: '',
27 | width: 0,
28 | height: 0,
29 | image_meta: {},
30 | sizes,
31 | },
32 | };
33 | }
34 |
35 | export function createMedia (file) {
36 | return new Promise(resolve => {
37 | const reader = new window.FileReader();
38 | reader.onload = () => {
39 | // Create media and add to list
40 | const img = getMedia(mediaList.length + 1, {
41 | media_type: file.type.split('/')[0],
42 | mime_type: file.type,
43 | source_url: reader.result,
44 | });
45 | mediaList.push(img);
46 | resolve(img);
47 | };
48 | reader.readAsDataURL(file);
49 | });
50 | }
51 |
52 |
53 | // Load media (images)
54 | mediaList.push(getMedia(1, {
55 | media_type: 'image',
56 | mime_type: 'image/jpeg',
57 | source_url: `${origin}/media/img1.jpg`,
58 | }));
59 |
60 | mediaList.push(getMedia(2, {
61 | media_type: 'image',
62 | mime_type: 'image/jpeg',
63 | source_url: `${origin}/media/img2.jpeg`,
64 | }));
65 |
66 | mediaList.push(getMedia(3, {
67 | media_type: 'image',
68 | mime_type: 'image/png',
69 | source_url: `${origin}/media/img3.png`,
70 | }));
71 |
72 |
73 | // Load media (videos)
74 | mediaList.push(getMedia(4, {
75 | media_type: 'video',
76 | mime_type: 'video/mp4',
77 | source_url: `${origin}/media/video1.mp4`,
78 | thumbnail: `${origin}/media/video1-thumb.jpg`,
79 | }));
80 |
81 | mediaList.push(getMedia(5, {
82 | media_type: 'video',
83 | mime_type: 'video/mp4',
84 | source_url: `${origin}/media/video2.mp4`,
85 | thumbnail: `${origin}/media/video2-thumb.jpg`,
86 | }));
87 |
88 |
89 | // Load media (audios)
90 | mediaList.push(getMedia(6, {
91 | media_type: 'audio',
92 | mime_type: 'audio/mp3',
93 | source_url: `${origin}/media/audio1.mp3`,
94 | thumbnail: `${origin}/media/audio1-thumb.png`,
95 | }));
96 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { BrowserRouter, Switch, Route } from 'react-router-dom';
4 |
5 | import './core';
6 |
7 | import Editor from './pages/editor';
8 | import Preview from './pages/preview';
9 |
10 | ReactDOM.render((
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ), document.getElementById('root'));
20 |
--------------------------------------------------------------------------------
/src/pages/editor.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import types from '../data/types';
3 | import { changeType } from '../globals/fake-data';
4 |
5 | import './editor.scss';
6 |
7 | const { data, editPost, domReady } = window.wp;
8 |
9 |
10 | class Editor extends React.Component {
11 | constructor (props) {
12 | super(props);
13 |
14 | let type = window.location.pathname.replace(/\//g, '');
15 | type = type.slice(0, -1);
16 |
17 | this.state = {
18 | postType: type || 'page',
19 | };
20 | }
21 |
22 | componentDidMount () {
23 | const { postType } = this.state;
24 |
25 | const settings = {
26 | alignWide: true,
27 | availableTemplates: [],
28 | allowedBlockTypes: true,
29 | disableCustomColors: false,
30 | disableCustomFontSizes: false,
31 | disablePostFormats: false,
32 | titlePlaceholder: 'Add title',
33 | bodyPlaceholder: 'Insert your custom block',
34 | isRTL: false,
35 | autosaveInterval: 3,
36 | style: [],
37 | imageSizes: [],
38 | richEditingEnabled: true,
39 | postLock: {
40 | isLocked: false,
41 | },
42 | postLockUtils: {
43 | nonce: '123456789',
44 | },
45 | enableCustomFields: true,
46 | mediaLibrary: true,
47 | __experimentalBlockPatterns: [],
48 | __experimentalBlockPatternCategories: [],
49 | __experimentalDisableCustomLineHeight: [],
50 | __experimentalDisableCustomUnits: [],
51 | __experimentalEnableLinkColor: [],
52 | };
53 |
54 | // Disable publish sidebar
55 | data.dispatch('core/editor').disablePublishSidebar();
56 |
57 | // Disable tips
58 | data.dispatch('core/nux').disableTips();
59 |
60 | // Initialize the editor
61 | window._wpLoadBlockEditor = new Promise(resolve => {
62 | domReady(() => {
63 | resolve(editPost.initializeEditor('editor', postType, 1, settings, {}));
64 | });
65 | });
66 | }
67 |
68 | resetLocalStorage = ev => {
69 | ev.preventDefault();
70 |
71 | localStorage.removeItem('g-editor-page');
72 | sessionStorage.removeItem('wp-autosave-block-editor-post-1');
73 | window.location.reload();
74 | };
75 |
76 | changePostType = (ev, type) => {
77 | ev.preventDefault();
78 | // update postType in localStorage before reload the editor
79 | const slug = type.slice(0, -1);
80 | changeType(slug);
81 |
82 | window.location.replace(type);
83 | };
84 |
85 | render () {
86 | const { postType } = this.state;
87 |
88 | return (
89 |
90 |
91 | {
92 | ['post', 'page'].map(type => {
93 | return (
94 | this.changePostType(ev, types[type].rest_base) }
98 | >{ types[type].name }
99 | );
100 | })
101 | }
102 |
103 | Clear page and reload
105 |
106 |
107 |
108 | );
109 | }
110 | }
111 |
112 | export default Editor;
113 |
--------------------------------------------------------------------------------
/src/pages/editor.scss:
--------------------------------------------------------------------------------
1 |
2 | .editor-nav {
3 | position: absolute;
4 | display: flex;
5 | justify-content: center;
6 | align-items: center;
7 | width: 100%;
8 | height: 56px;
9 |
10 | * {
11 | z-index: 99;
12 | }
13 | }
14 |
15 | // TODO: move to core style.
16 | @media (min-width: 782px) {
17 | .edit-post-layout__content {
18 | margin-left: 0 !important;
19 | }
20 | }
21 |
22 | iframe {
23 | border: none;
24 | }
25 |
--------------------------------------------------------------------------------
/src/pages/preview.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { getPage } from '../globals/fake-data';
3 |
4 | const { domReady } = window.wp;
5 |
6 |
7 | class Preview extends React.Component {
8 | constructor (props) {
9 | super(props);
10 |
11 | this.state = {
12 | rendered: '',
13 | };
14 | }
15 |
16 | componentWillMount () {
17 | // remove block editor style from page
18 | const editorStyle = document.querySelector('style[id="block-editor-style"]');
19 | if (editorStyle) {
20 | editorStyle.remove();
21 | }
22 |
23 | // remove editor style
24 | const style = document.querySelector('link[href$="css/gutenberg/style.css"]');
25 | if (style) {
26 | style.remove();
27 | }
28 | }
29 |
30 | componentDidMount () {
31 | const page = getPage();
32 | const rendered = (
33 | page && page.content && page.content.raw &&
34 | page.content.raw.replace(/()/g, '')
35 | ) || '';
36 |
37 | this.setState({
38 | rendered,
39 | });
40 |
41 | domReady(() => {
42 | // Load the frontend scripts
43 | const code = document.getElementById('frontend-scripts');
44 | if (code && code.innerText) {
45 | const script = document.createElement('script');
46 | script.type = 'text/javascript';
47 | script.async = true;
48 | script.src = `data:text/javascript;base64,${code.innerText}`;
49 | document.body.appendChild(script);
50 | }
51 |
52 | // Load html blocks scripts
53 | const html = rendered.trim();
54 | const container = document.createElement('div');
55 | container.innerHTML = html;
56 |
57 | const scripts = container.getElementsByTagName('script');
58 | for(const s of scripts) {
59 | const script = document.createElement('script');
60 | script.type = 'text/javascript';
61 | script.async = true;
62 |
63 | if (s.innerText) {
64 | // inner script
65 | const frontendScript = Buffer.from(s.innerText).toString('base64');
66 | script.src = `data:text/javascript;base64,${frontendScript}`;
67 | }
68 | else {
69 | // or from external src
70 | script.src = s.src;
71 | }
72 |
73 | document.body.appendChild(script);
74 | }
75 | });
76 | }
77 |
78 | render () {
79 | const { rendered } = this.state;
80 | return rendered ?
81 |
:
82 | Add your custom block in the editor ;
83 | }
84 | }
85 |
86 | export default Preview;
87 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read http://bit.ly/CRA-PWA.
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register (config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA'
47 | );
48 | });
49 | }
50 | else {
51 | // Is not localhost. Just register service worker
52 | registerValidSW(swUrl, config);
53 | }
54 | });
55 | }
56 | }
57 |
58 | function registerValidSW (swUrl, config) {
59 | navigator.serviceWorker
60 | .register(swUrl)
61 | .then(registration => {
62 | registration.onupdatefound = () => {
63 | const installingWorker = registration.installing;
64 | installingWorker.onstatechange = () => {
65 | if (installingWorker.state === 'installed') {
66 | if (navigator.serviceWorker.controller) {
67 | // At this point, the updated precached content has been fetched,
68 | // but the previous service worker will still serve the older
69 | // content until all client tabs are closed.
70 | console.log(
71 | 'New content is available and will be used when all ' +
72 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
73 | );
74 |
75 | // Execute callback
76 | if (config && config.onUpdate) {
77 | config.onUpdate(registration);
78 | }
79 | }
80 | else {
81 | // At this point, everything has been precached.
82 | // It's the perfect time to display a
83 | // "Content is cached for offline use." message.
84 | console.log('Content is cached for offline use.');
85 |
86 | // Execute callback
87 | if (config && config.onSuccess) {
88 | config.onSuccess(registration);
89 | }
90 | }
91 | }
92 | };
93 | };
94 | })
95 | .catch(error => {
96 | console.error('Error during service worker registration:', error);
97 | });
98 | }
99 |
100 | function checkValidServiceWorker (swUrl, config) {
101 | // Check if the service worker can be found. If it can't reload the page.
102 | fetch(swUrl)
103 | .then(response => {
104 | // Ensure service worker exists, and that we really are getting a JS file.
105 | if (
106 | response.status === 404 ||
107 | response.headers.get('content-type').indexOf('javascript') === -1
108 | ) {
109 | // No service worker found. Probably a different app. Reload the page.
110 | navigator.serviceWorker.ready.then(registration => {
111 | registration.unregister().then(() => {
112 | window.location.reload();
113 | });
114 | });
115 | }
116 | else {
117 | // Service worker found. Proceed as normal.
118 | registerValidSW(swUrl, config);
119 | }
120 | })
121 | .catch(() => {
122 | console.log(
123 | 'No internet connection found. App is running in offline mode.'
124 | );
125 | });
126 | }
127 |
128 | export function unregister () {
129 | if ('serviceWorker' in navigator) {
130 | navigator.serviceWorker.ready.then(registration => {
131 | registration.unregister();
132 | });
133 | }
134 | }
135 |
--------------------------------------------------------------------------------