diff --git a/iceberg.php b/iceberg.php index 0e83e75..97a148f 100644 --- a/iceberg.php +++ b/iceberg.php @@ -3,7 +3,7 @@ * Plugin Name: Iceberg Editor * Plugin URI: https://useiceberg.com/ * Description: Iceberg is a beautiful, flexible markdown editor for writing within the WordPress block editor. Iceberg leverages the best of WordPress, and the best of the new block editor, while getting out of your way – allowing you to focus on publishing your next post. - * Version: 1.0.0 + * Version: 1.1.0 * Author: Iceberg * Author URI: https://useiceberg.com/ * Text Domain: iceberg diff --git a/src/components/theme-import-export/file.js b/src/components/theme-import-export/file.js new file mode 100644 index 0000000..f820e84 --- /dev/null +++ b/src/components/theme-import-export/file.js @@ -0,0 +1,59 @@ +/** + * Downloads a file. + * + * @param {string} fileName File Name. + * @param {string} content File Content. + * @param {string} contentType File mime type. + */ +export function download( fileName, content, contentType ) { + const file = new window.Blob( [ content ], { type: contentType } ); + + // IE11 can't use the click to download technique + // we use a specific IE11 technique instead. + if ( window.navigator.msSaveOrOpenBlob ) { + window.navigator.msSaveOrOpenBlob( file, fileName ); + } else { + const a = document.createElement( 'a' ); + a.href = URL.createObjectURL( file ); + a.download = fileName; + + a.style.display = 'none'; + document.body.appendChild( a ); + a.click(); + document.body.removeChild( a ); + } +} + +/** + * Reads the textual content of the given file. + * + * @param {File} file File. + * @return {Promise} Content of the file. + */ +export function readTextFile( file ) { + const reader = new window.FileReader(); + return new Promise( ( resolve ) => { + reader.onload = function() { + resolve( reader.result ); + }; + reader.readAsText( file ); + } ); +} + +/** + * Import a reusable block from a JSON file. + * + * @param {File} file File. + * @return {Promise} Promise returning the imported reusable block. + */ +export async function importThemeSettings( file ) { + const fileContent = await readTextFile( file ); + let parsedContent; + try { + parsedContent = JSON.parse( fileContent ); + } catch ( e ) { + throw new Error( 'Invalid JSON file' ); + } + + return parsedContent; +} diff --git a/src/components/theme-import-export/index.js b/src/components/theme-import-export/index.js new file mode 100644 index 0000000..652e5c1 --- /dev/null +++ b/src/components/theme-import-export/index.js @@ -0,0 +1,149 @@ +/** + * Internal dependencies + */ +import { download, importThemeSettings } from './file'; +import icons from '../icons'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Fragment, Component } from '@wordpress/element'; +import { withDispatch, withSelect } from '@wordpress/data'; +import { compose, withInstanceId } from '@wordpress/compose'; +import { + BaseControl, + MenuGroup, + MenuItem, + DropZoneProvider, + DropZone, + withSpokenMessages, + Button, +} from '@wordpress/components'; + +class ThemeImportExport extends Component { + constructor() { + super( ...arguments ); + + this.exportAsJSON = this.exportAsJSON.bind( this ); + this.onFilesUpload = this.onFilesUpload.bind( this ); + this.state = { + isImported: false, + }; + } + + exportAsJSON() { + const { themeSettings } = this.props; + + //do export magic + const fileContent = JSON.stringify( themeSettings, null, 2 ); + + const fileName = 'iceberg-theme-settings.json'; + download( fileName, fileContent, 'application/json' ); + } + + onFilesUpload( files ) { + const { updateThemeSettings, loadConfig, updateState } = this.props; + let file = files[ 0 ]; + + if ( files.target ) { + file = event.target.files[ 0 ]; + } + + if ( ! file ) { + return; + } + + importThemeSettings( file ) + .then( ( importedSettings ) => { + updateState( 'themeSettings', importedSettings ); + updateState( 'theme', importedSettings.theme ); + updateThemeSettings( importedSettings ); + this.setState( { isImported: true } ); + + // reload variables + loadConfig( importedSettings.theme, importedSettings ); + } ) + .catch( () => { + this.setState( { error: true } ); + } ); + } + + render() { + const { onToggle, onClose } = this.props; + + return ( + +
+ + { __( 'Export theme settings', 'iceberg' ) } + + + + { __( 'Import', 'iceberg' ) } + +
+ { __( + 'Drag and drop iceberg-theme-settings.json file in here.', + 'iceberg' + ) } + +
+
+
+ + { + onClose(); + onToggle(); + onToggle(); + + // focus manually to fix closing outside bug + document + .querySelector( + '.components-iceberg-theme-switcher__content .components-popover__content' + ) + .focus(); + } } + > + { __( 'Back to editor themes', 'iceberg' ) } + { icons.back } + + +
+ ); + } +} + +export default compose( [ + withInstanceId, + withSelect( ( select ) => { + const { getThemeSettings } = select( 'iceberg-settings' ); + + return { + themeSettings: getThemeSettings(), + }; + } ), + withDispatch( ( dispatch ) => { + const { setThemeSettings } = dispatch( 'iceberg-settings' ); + + return { + updateThemeSettings( settings ) { + setThemeSettings( settings ); + }, + }; + } ), + withSpokenMessages, +] )( ThemeImportExport ); diff --git a/src/components/theme-import-export/style.scss b/src/components/theme-import-export/style.scss new file mode 100644 index 0000000..47334aa --- /dev/null +++ b/src/components/theme-import-export/style.scss @@ -0,0 +1,3 @@ +.components-iceberg-theme-switcher__import-export { + padding: 12px; +} diff --git a/src/components/theme-switcher/index.js b/src/components/theme-switcher/index.js index 88bea55..fd46d97 100644 --- a/src/components/theme-switcher/index.js +++ b/src/components/theme-switcher/index.js @@ -9,6 +9,7 @@ import { map, merge, assign, get } from 'lodash'; import defaults from '../theme-editor/default'; import EditorThemes from '../theme-editor/editor-themes'; import ThemeEditor from '../theme-editor'; +import ThemeImportExport from '../theme-import-export'; import icons from '../icons'; import { assignVariables } from './variables'; import difference from './utils/difference'; @@ -47,6 +48,7 @@ class ThemeSwitcher extends Component { isEditorThemeLoaded: false, isEditingTheme: false, isEditingTypography: false, + isImportExport: false, }; } @@ -162,7 +164,10 @@ class ThemeSwitcher extends Component { } } - this.setState( { isEditingTypography: false } ); + this.setState( { + isEditingTypography: false, + isImportExport: false, + } ); updateThemeSettings( this.state.themeSettings ); }; @@ -269,7 +274,8 @@ class ThemeSwitcher extends Component { renderContent={ ( { onToggle } ) => ( { ! this.state.isEditingTheme && - ! this.state.isEditingTypography ? ( + ! this.state.isEditingTypography && + ! this.state.isImportExport ? ( { map( @@ -358,9 +364,30 @@ class ThemeSwitcher extends Component { ) } { icons.typography } + { + this.setState( { + isEditingTheme: false, + isEditingTypography: false, + isImportExport: true, + } ); + this.onEditTheme( + onToggle, + 'isImportExport' + ); + } } + > + { __( + 'Import / Export', + 'iceberg' + ) } + - ) : ( + ) : null } + { ! this.state.isEditingTheme && + ! this.state.isEditingTypography ? null : ( ) } + + { this.state.isImportExport && ( + { + this.setState( { + isEditingTheme: false, + isEditingTypography: false, + isImportExport: false, + } ); + this.onExitEditTheme( onToggle ); + } } + /> + ) } ) } /> diff --git a/src/style.scss b/src/style.scss index 2e64a23..772d7bc 100644 --- a/src/style.scss +++ b/src/style.scss @@ -19,6 +19,7 @@ $imageWidth: 230px; @import "./components/document-info/style.scss"; @import "./components/settings/style.scss"; @import "./components/block-indicator/style.scss"; + @import "./components/theme-import-export/style.scss"; @import "./components/contextual-toolbar/style.scss"; //Editor styling