From 4cbda378b1c2a0bd7c143c708fbc885e538f3f09 Mon Sep 17 00:00:00 2001 From: Drapich Piotr Date: Mon, 6 Jan 2020 16:24:56 +0100 Subject: [PATCH] [Monorepo][base-branch] import of gutenberg-mobile (#18508) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use string-array instead of plurals tag in strings.xml See https://github.com/GlotPress/GlotPress-WP/blob/master/gp-includes/formats/format-android.php * Update string files * Update scripts to minimize changes in git diff and keep the same context for android * Exclude strings from tests * Fix lint errors * Bump version to 1.17.1 * Update gutenberg ref following 1.17 merge to gutenberg master * Update gutenberg ref * Remove declaration on bridge of unused methods. * Added bridge code for gutenberg to request a native fullscreen preview for for an image from a URL on iOS. * Updating bundle and gutenberg reference. * Updated release notes. * Update gutenberg ref * Update gutenberg ref * Update gutenberg ref * Update GB reference. * Updating bundle after catching up branch and gutenberg submodule. * Update gutenberg ref * Update release notes. * Update UI test * Pass postType as initial prop on iOS * Pass postType down to Editor * Allow Android to set the post type * Updating gutenberg reference. * Removed duplicate line from merge. * updafe test files and iOS version for running locally with xcode 11 * Update gutenberg ref * Update gutenberg ref * Update aztec to version that support reversed and start on lists. * Update Gutenberg to version where list settings are active in native. * update block list check and capabilities * replace double click on android * fix block insertion timeout * update branch with develop * Update gutenberg ref * Update package.json and JS bundle for 1.18.0 release * Update Gutenberg * Update Gutenberg * update gutenberg ref * Update GB-reference. * Update release notes. * revert caps to iPhone Simulator * Update Gutenberg ref * Update Release notes * Update gutenberg/ reference * Update bundles * Update GB reference. * Fix spacing * Add static method to Media class to create instance using mimeType * Add flag to track when appending multiple selected media items as blocks * Introduce mediaSelectionCancelled method in WPAndroidGlueCode * Set flag to append blocks when multiple = false is not respected * Only use appendUploadMediaFiles plural version * Update gutenberg ref * Update to latest Gutenberg master * Patch jsdom to implement Element.closest() * Bring back changes on package.json from 1.7.1 * Add docstring to the function * Return null as per https://dom.spec.whatwg.org/#dom-element-closest * Update Aztec version. * Update GB reference. * Update gutenberg ref * Update RELEASE-NOTES.txt * Update GB reference * Improve code block style * Update GB reference. * Update gutenberg ref * Update Media mimeType truncation to use enum names * Add Javadoc for mAppendsMultipleSelectedToSiblingBlocks flag * Set appends to sibling blocks flag explicitly for all requests * Add clarifying comment for special block append handling * Remove singular (unused) appendMedia method * Update GB reference. * Update GB reference to master. * Set appends to sibling blocks flag explicitly for other media pick * Update GB reference. * Update GB reference. * Use lowercase for Media mimeType truncation * Update Gutenberg * Update Gutenberg * Update Gutenberg ref - after fix for caption alignment * Update bundles * Point to aztecVersion hash which supports list with start and reverse attribute * Update bundle and gutenberg ref - fix disappearing image * Update GB reference. * Update GB reference. * Update GB reference. * Update Gutenberg * Update Gutenberg ref * Update GB reference * Update GB reference. * Only enable page templates on dev builds * Update aztec version * Update aztec version * Add colors for gallery block * Update gutenberg reference * Update gutenberg reference * Update gutenberg reference * Update GB reference. * Update Gutenberg ref * Update Aztec version to 1.14.0 * Make sure if the textColor is changed the default text color is updated. * Use Slider from react-native-community lib (#1620) * Use Slider from react-native-community lib * Update slider version * Add function to read npm version * Rengenerate yarn.lock * Bump node version * Update readNPMVersion function * Use Slider from fork * Update Slider commit * Use Slider from wordpress-mobile fork * Add react-native-slider podspec * Improve babel config * Correct settings.gradle * Correct project.pbxproj * Update ref * Bump * Update ref * Update ref to gutenberg master * Update ref to gutenberg quick fix * Update ref to gutenberg master * Update Aztec editor version. * Update to iOS 13 * Update Xcode version. * Use iOS 12 for tests. * Fix typo * Update Aztec to fix CI error with xcode 10 * Use iOS 12.2 * Update Appium version. * Update to appium 1.15.1 * Update to Appium 1.15.1 only in iOS * Update caps. * Add Gallery Block (#1498) * WIP - initial-html.js for gallery testing * Add parent app media mock for Android * Update gutenberg reference * Update gutenberg reference * Comment out line setting mPendingMediaUploadCallback to null * Update gutenberg reference * Generate bundles * Update gutenberg ref * Generate bundles * Generate bundles * Add some color-studio colors for gallery * Update gutenberg reference * Generate bundle * WIP update ref * Update gutenberg reference * Update gutenberg submodule * Generate bundles * Add $gray-40 color * Update gutenberg reference * Generate bundles * Update gutenberg reference * Generate bundles * Update gutenberg reference * Generate bundles * Update gutenberg reference * Generate bundles * Update gutenberg reference * Update gutenberg reference * Update gutenberg reference * Update gutenberg reference * Update gutenberg reference * Update gutenberg reference * Update gutenberg reference * Update gutenberg reference * Restore demo content * Restore anonymous implementation of GutenbergBridgeJS2Parent * Update gutenberg reference * Generate bundles * Bump up Aztec version on iOS Example app * Update gb ref * Generate bundles * Update gutenberg ref * Update gutenberg ref (#1646) * Add release notes for Gallery (#1658) * Make sure we use iPhone 11 (iOS 13) for build and run tests * Pooint aztecVersion to develop * Update Appium for Android tests too. * Set Appium to 1.15.0 * Update aztec version to 1.3.36 * Update appium to 1.16.0-rc.1 * Update Aztec iOS to 1.14.1 * Update GB reference. * Activate preformat block on android platform (#1615) * Updates package.json and JS bundle for 1.19.0 release. * Update to shorten git commands Make the git commands a little easier to copy by taking out `$` from the start of the lines. This also matches with the other commands on the page which do not start with `$`. * Update Unit Tests headings in Getting Started documentation The heading "Test" should be "Unit Tests". The heading "Writing and Running Tests" should be "Writing and Running Unit Tests". https://github.com/wordpress-mobile/gutenberg-mobile/blob/develop/src/index.test.js * Update gutenberg reference * Update gutenberg reference * Update gutenberg ref * Updating bundles. * Feat: Navigation Down in InnerBlocks (#1379) * Add ref to gutenberg repo * Fix e2e tests * Update ref to gutenberg master * Update Gutenberg ref * Remove empty line between checkboxes * Updating release notes to show video settings in 1.19 * Updating gutenberg reference to latest 1.19 release change on gutenberg master branch * Update gutenberg ref * Sass Transformer: Also look for partials Add support for [SASS partials](https://sass-lang.com/guide), as is already present [upstream](https://github.com/kristerkari/react-native-sass-transformer/blob/52884dd59582856fa17e2b2e8ca9efc37d412387/index.js#L41-L42). This should fix the issue introduced by https://github.com/WordPress/gutenberg/pull/19159, and discussed there. * Single quotes * Also update sass-transformer-inside-gb.js * Update release notes * Update gutenberg ref * Update gutenberg ref * Update gutenberg ref * Update gutenberg ref * Update Gutenberg ref * Update gutenberg ref * Update gutenberg ref * Bundles up to date with merged code from develop * Add ref to gutenberg repo * Update ref * Update ref * Update ref * Upgrade the SVG lib to fix #1703 * Upgrade the Video lib to fix #1705 * Upgrade the Slider lib to use node_modules in local npm build * Update ref * Upgrade the SVG lib ref * Upgrade the Video lib ref * Upgrade the Slider lib ref * Update ref * Brings back gb master to normal (#1722) * Fix/Bring back master to normal (#1724) * Update Gutenberg ref * Update Gutenberg ref Co-authored-by: Tugdual de Kerviler Co-authored-by: Matt Chowning Co-authored-by: etoledom Co-authored-by: Sérgio Estêvão Co-authored-by: Cameron Voell Co-authored-by: Jorge Bernal Co-authored-by: Javon Davis Co-authored-by: Maxime Biais Co-authored-by: Gerardo Pacheco Co-authored-by: Matthew Kevins Co-authored-by: Stefanos Togoulidis Co-authored-by: Marko Savic Co-authored-by: Luke Walczak Co-authored-by: Pinar Olguc Co-authored-by: Sheri Bigelow Co-authored-by: jbinda Co-authored-by: Bernie Reiter --- .../react-native-editor/.circleci/config.yml | 177 + packages/react-native-editor/.eslintignore | 7 + packages/react-native-editor/.eslintrc.js | 74 + packages/react-native-editor/.flowconfig | 115 + packages/react-native-editor/.gitattributes | 1 + .../.github/ISSUE_TEMPLATE/bug_report.md | 32 + .../.github/ISSUE_TEMPLATE/feature_request.md | 20 + .../.github/PULL_REQUEST_TEMPLATE.md | 8 + packages/react-native-editor/.gitignore | 109 + packages/react-native-editor/.gitmodules | 6 + packages/react-native-editor/.prettierignore | 1 + .../.vscode/extensions.json | 6 + .../react-native-editor/.vscode/launch.json | 14 + .../react-native-editor/.vscode/settings.json | 9 + packages/react-native-editor/.watchmanconfig | 1 + .../DependencyGraph.js.patched | 297 + .../react-native-editor/Gutenberg.podspec | 26 + packages/react-native-editor/LICENSE | 339 + packages/react-native-editor/README.md | 168 + .../react-native-editor/RELEASE-NOTES.txt | 102 + .../react-native-editor/RNTAztecView.podspec | 23 + .../__device-tests__/CONTRIBUTING.md | 44 + .../__device-tests__/README.md | 56 + .../gutenberg-editor-block-insertion.test.js | 124 + .../gutenberg-editor-heading.test.js | 76 + .../gutenberg-editor-image.test.js | 107 + .../gutenberg-editor-lists-end.test.js | 71 + .../gutenberg-editor-lists.test.js | 72 + .../gutenberg-editor-paragraph.test.js | 128 + .../gutenberg-editor-paste.test.js | 126 + .../gutenberg-editor-rotation.test.js | 83 + .../__device-tests__/helpers/appium-local.js | 49 + .../__device-tests__/helpers/caps.js | 25 + .../__device-tests__/helpers/serverConfigs.js | 10 + .../__device-tests__/helpers/test-data.js | 89 + .../__device-tests__/helpers/utils.js | 352 + .../__device-tests__/pages/editor-page.js | 417 + packages/react-native-editor/android/app/BUCK | 55 + .../android/app/build.gradle | 164 + .../android/app/build_defs.bzl | 19 + .../android/app/proguard-rules.pro | 17 + .../android/app/src/debug/AndroidManifest.xml | 5 + .../src/debug/res/xml/react_native_config.xml | 8 + .../android/app/src/main/AndroidManifest.xml | 29 + .../main/java/com/gutenberg/MainActivity.java | 15 + .../java/com/gutenberg/MainApplication.java | 171 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3056 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5024 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2096 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2858 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4569 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7098 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6464 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10676 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9250 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15523 bytes .../app/src/main/res/values/strings.xml | 3 + .../app/src/main/res/values/styles.xml | 8 + .../react-native-editor/android/build.gradle | 39 + .../android/gradle.properties | 20 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + packages/react-native-editor/android/gradlew | 172 + .../react-native-editor/android/gradlew.bat | 84 + .../android/keystores/BUCK | 8 + .../keystores/debug.keystore.properties | 4 + .../android/settings.gradle | 15 + packages/react-native-editor/app.json | 7 + packages/react-native-editor/babel.config.js | 53 + .../react-native-editor/bin/ci-checks-js.sh | 36 + .../bin/generate-podspecs.sh | 55 + .../react-native-editor/bin/po2android.js | 118 + packages/react-native-editor/bin/po2swift.js | 47 + .../bin/sauce-pre-upload.sh | 9 + .../bin/update-bintray-repo.sh | 77 + .../react-native-editor/bundle/android/App.js | 1800 ++ .../bundle/android/App.js.map | 1 + .../bundle/android/strings.xml | 104 + .../react-native-editor/bundle/ios/App.js | 1805 ++ .../react-native-editor/bundle/ios/App.js.map | 1 + .../ios/GutenbergNativeTranslations.swift | 86 + .../react-native-editor/docs/Releasing.md | 30 + .../extra-node-modules.config.js | 7 + packages/react-native-editor/gutenberg | 1 + .../react-native-editor/i18n-cache/.gitignore | 3 + .../i18n-cache/data/.gitignore | 3 + .../react-native-editor/i18n-cache/index.js | 145 + .../images/recommended-extensions.png | Bin 0 -> 13624 bytes packages/react-native-editor/index.js | 9 + .../ios/CustomImageLoader.swift | 36 + .../ios/gutenberg-Bridging-Header.h | 8 + .../ios/gutenberg-tvOS/Info.plist | 54 + .../ios/gutenberg-tvOSTests/Info.plist | 24 + .../ios/gutenberg.xcodeproj/project.pbxproj | 1939 +++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcschemes/gutenberg-tvOS.xcscheme | 129 + .../xcshareddata/xcschemes/gutenberg.xcscheme | 129 + .../ios/gutenberg/AppDelegate.swift | 20 + .../ios/gutenberg/Base.lproj/LaunchScreen.xib | 42 + .../gutenberg/GutenbergViewController.swift | 289 + .../AppIcon.appiconset/Contents.json | 53 + .../gutenberg/Images.xcassets/Contents.json | 6 + .../aztec.imageset/Contents.json | 21 + .../Images.xcassets/aztec.imageset/aztec.png | Bin 0 -> 6470 bytes .../ios/gutenberg/Info.plist | 79 + .../ios/gutenberg/MediaPickCoordinator.swift | 89 + .../ios/gutenberg/MediaProvider.swift | 58 + .../gutenberg/MediaUploadCoordinator.swift | 93 + .../ios/gutenbergTests/Info.plist | 24 + .../ios/gutenbergTests/gutenbergTests.m | 68 + packages/react-native-editor/jest.config.js | 60 + .../react-native-editor/jest_gb.config.js | 9 + .../react-native-editor/jest_ui.config.js | 17 + packages/react-native-editor/jitpack.yml | 9 + packages/react-native-editor/libdefs.js | 12 + packages/react-native-editor/package.json | 189 + .../react-native-aztec-old-submodule | 1 + .../react-native-aztec/.flowconfig | 11 + .../react-native-aztec/.gitignore | 118 + .../react-native-aztec/LICENSE | 339 + .../react-native-aztec/README.md | 72 + .../react-native-aztec/android/build.gradle | 118 + .../android/gradle.properties | 3 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 56177 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + .../react-native-aztec/android/gradlew | 172 + .../react-native-aztec/android/gradlew.bat | 84 + .../android/settings.gradle | 2 + .../android/src/main/AndroidManifest.xml | 20 + .../ReactAztecArrowKeyMovementMethod.java | 21 + .../ReactAztecBackSpaceEvent.java | 49 + .../ReactNativeAztec/ReactAztecBlurEvent.java | 39 + .../ReactAztecEndEditingEvent.java | 46 + .../ReactAztecEnterEvent.java | 56 + .../ReactAztecFocusEvent.java | 39 + .../ReactAztecFormattingChangeEvent.java | 45 + .../ReactNativeAztec/ReactAztecManager.java | 681 + .../ReactNativeAztec/ReactAztecPackage.java | 27 + .../ReactAztecPasteEvent.java | 56 + .../ReactAztecSelectionChangeEvent.java | 53 + .../ReactNativeAztec/ReactAztecText.java | 574 + .../ReactAztecTextFormatEnum.java | 42 + .../ReactAztecTextShadowNode.java | 24 + .../ReactNativeAztec/EnterPressedWatcher.kt | 71 + .../main/res/drawable-hdpi/ic_launcher.png | Bin 0 -> 3879 bytes .../main/res/drawable-mdpi/ic_launcher.png | Bin 0 -> 2411 bytes .../main/res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 5376 bytes .../main/res/drawable-xxhdpi/ic_launcher.png | Bin 0 -> 9572 bytes .../android/src/main/res/values/bools.xml | 7 + .../android/src/main/res/values/integers.xml | 7 + .../android/src/main/res/values/strings.xml | 20 + .../src/main/res/values/template-dimens.xml | 25 + .../react-native-aztec/example/.babelrc | 3 + .../react-native-aztec/example/App.js | 83 + .../example/android/app/build.gradle | 159 + .../android/app/src/main/AndroidManifest.xml | 44 + .../com/example/android/MainActivity.java | 59 + .../java/com/example/android/MyFragment.java | 55 + .../activities/SampleRNBaseActivity.java | 86 + .../main/res/drawable-hdpi/ic_launcher.png | Bin 0 -> 3879 bytes .../app/src/main/res/drawable-hdpi/tile.9.png | Bin 0 -> 196 bytes .../main/res/drawable-mdpi/ic_launcher.png | Bin 0 -> 2411 bytes .../main/res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 5376 bytes .../main/res/drawable-xxhdpi/ic_launcher.png | Bin 0 -> 9572 bytes .../res/drawable/ic_reorder_black_24dp.xml | 9 + .../main/res/layout-w720dp/activity_main.xml | 36 + .../app/src/main/res/layout/activity_main.xml | 35 + .../android/app/src/main/res/menu/main.xml | 23 + .../res/values-sw600dp/template-dimens.xml | 24 + .../res/values-sw600dp/template-styles.xml | 25 + .../app/src/main/res/values/colors.xml | 23 + .../app/src/main/res/values/dimens.xml | 20 + .../main/res/values/fragmentview_strings.xml | 19 + .../app/src/main/res/values/strings.xml | 23 + .../app/src/main/res/values/styles.xml | 8 + .../src/main/res/values/template-dimens.xml | 32 + .../src/main/res/values/template-styles.xml | 45 + .../example/android/build.gradle | 46 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + .../example/android/gradlew | 164 + .../example/android/gradlew.bat | 90 + .../example/android/settings.gradle | 7 + .../react-native-aztec/example/app.json | 4 + .../react-native-aztec/example/content.js | 94 + .../react-native-aztec/example/editor.js | 73 + .../example/iOS/example-Bridging-Header.h | 7 + .../iOS/example.xcodeproj/project.pbxproj | 1301 ++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/example.xcscheme | 129 + .../example/iOS/example/AppDelegate.swift | 58 + .../iOS/example/Base.lproj/LaunchScreen.xib | 42 + .../example/iOS/example/BridgeDelegate.swift | 27 + .../AppIcon.appiconset/Contents.json | 53 + .../iOS/example/Images.xcassets/Contents.json | 6 + .../aztec.imageset/Contents.json | 21 + .../Images.xcassets/aztec.imageset/aztec.png | Bin 0 -> 6470 bytes .../example/iOS/example/Info.plist | 56 + .../example/iOS/example/MediaProvider.swift | 59 + .../example/iOS/exampleTests/Info.plist | 24 + .../example/iOS/exampleTests/exampleTests.m | 68 + .../react-native-aztec/example/index.js | 4 + .../react-native-aztec/example/package.json | 35 + .../example/rn-cli.config.js | 8 + .../react-native-aztec/example/yarn.lock | 6266 +++++++ .../react-native-aztec/index.js | 3 + .../react-native-aztec/ios/Cartfile | 1 + .../react-native-aztec/ios/Cartfile.resolved | 1 + .../RNTAztecView.xcodeproj/project.pbxproj | 428 + .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../ios/RNTAztecView/BlockFormatHandler.swift | 15 + .../ios/RNTAztecView/BlockModel.swift | 3 + .../HeadingBlockFormatHandler.swift | 40 + .../RCTAztecView-Bridging-Header.h | 7 + .../ios/RNTAztecView/RCTAztecView.swift | 674 + .../ios/RNTAztecView/RCTAztecViewManager.m | 32 + .../RNTAztecView/RCTAztecViewManager.swift | 69 + .../react-native-aztec/package.json | 28 + .../react-native-aztec/src/AztecView.js | 184 + .../react-native-aztec/yarn.lock | 1247 ++ .../.gitattributes | 1 + .../react-native-gutenberg-bridge/.gitignore | 46 + .../react-native-gutenberg-bridge/README.md | 44 + .../android/build.gradle | 179 + .../android/gradle.properties | 3 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + .../android/gradlew | 172 + .../android/gradlew.bat | 84 + .../android/settings.gradle | 5 + .../android/src/debug/AndroidManifest.xml | 11 + .../android/src/main/AndroidManifest.xml | 5 + .../GutenbergBridgeJS2Parent.java | 109 + .../RNReactNativeGutenbergBridgeModule.java | 264 + .../RNReactNativeGutenbergBridgePackage.java | 35 + .../wordpress/mobile/WPAndroidGlue/Media.java | 73 + .../mobile/WPAndroidGlue/MediaOption.java | 33 + .../OkHttpHeaderInterceptor.java | 34 + .../mobile/WPAndroidGlue/RequestExecutor.java | 7 + .../WPAndroidGlue/WPAndroidGlueCode.java | 694 + .../react-native-gutenberg-bridge/index.js | 112 + .../ios/Gutenberg.swift | 205 + .../ios/GutenbergBridgeDataSource.swift | 52 + .../ios/GutenbergBridgeDelegate.swift | 144 + ...actNativeGutenbergBridge-Bridging-Header.h | 17 + .../ios/RNReactNativeGutenbergBridge.m | 21 + .../ios/RNReactNativeGutenbergBridge.swift | 258 + .../project.pbxproj | 306 + .../contents.xcworkspacedata | 9 + .../package.json | 19 + .../DoubleConversion.podspec.json | 23 + .../third-party-podspecs/Folly.podspec.json | 91 + ...RNReactNativeRecyclerviewList.podspec.json | 25 + .../third-party-podspecs/RNSVG.podspec.json | 27 + .../React-ART.podspec.json | 29 + .../React-Core.podspec.json | 72 + .../React-DevSupport.podspec.json | 33 + .../React-Fabric.podspec.json | 318 + .../React-RCTActionSheet.podspec.json | 30 + .../React-RCTAnimation.podspec.json | 29 + .../React-RCTBlob.podspec.json | 38 + .../React-RCTCameraRoll.podspec.json | 33 + .../React-RCTFabric.podspec.json | 48 + .../React-RCTImage.podspec.json | 33 + .../React-RCTLinking.podspec.json | 30 + .../React-RCTNetwork.podspec.json | 29 + .../React-RCTPushNotification.podspec.json | 30 + .../React-RCTSettings.podspec.json | 30 + .../React-RCTTest.podspec.json | 30 + .../React-RCTText.podspec.json | 30 + .../React-RCTVibration.podspec.json | 30 + .../React-RCTWebSocket.podspec.json | 30 + .../React-cxxreact.podspec.json | 37 + .../React-graphics.podspec.json | 34 + .../React-jscallinvoker.podspec.json | 31 + .../React-jsi.podspec.json | 46 + .../React-jsiexecutor.podspec.json | 36 + .../React-jsinspector.podspec.json | 19 + .../React-turbomodule-core.podspec.json | 49 + .../React-turbomodule-samples.podspec.json | 52 + .../third-party-podspecs/React.podspec.json | 62 + .../ReactNativeDarkMode.podspec.json | 23 + .../third-party-podspecs/glog.podspec.json | 43 + ...ve-keyboard-aware-scroll-view.podspec.json | 22 + .../react-native-safe-area.podspec.json | 22 + .../react-native-slider.podspec.json | 23 + .../react-native-video.podspec.json | 45 + .../third-party-podspecs/yoga.podspec.json | 33 + .../resources/fonts/NotoSerif-Bold.ttf | Bin 0 -> 248544 bytes .../resources/fonts/NotoSerif-BoldItalic.ttf | Bin 0 -> 263732 bytes .../resources/fonts/NotoSerif-Italic.ttf | Bin 0 -> 250400 bytes .../resources/fonts/NotoSerif-Regular.ttf | Bin 0 -> 247392 bytes .../rn-cli-inside-gb.config.js | 39 + packages/react-native-editor/rn-cli.config.js | 22 + .../sass-transformer-inside-gb.js | 155 + .../react-native-editor/sass-transformer.js | 162 + packages/react-native-editor/src/_colors.scss | 146 + .../src/_native.android.scss | 13 + .../react-native-editor/src/_native.ios.scss | 13 + .../src/api-fetch-setup.js | 39 + packages/react-native-editor/src/globals.js | 60 + packages/react-native-editor/src/index.js | 100 + .../react-native-editor/src/index.test.js | 82 + .../react-native-editor/src/initial-html.js | 218 + .../react-native-editor/src/jsdom-patches.js | 240 + .../src/parser/block-parser-code.test.js | 43 + .../src/parser/block-parser-more.test.js | 37 + .../src/parser/block-parser-paragraph.test.js | 43 + .../react-native-editor/src/store/types.js | 18 + .../src/test/api-fetch-setup.test.js | 32 + .../@wordpress/a11y | 1 + .../@wordpress/api-fetch | 1 + .../@wordpress/autop | 1 + .../@wordpress/blob | 1 + .../@wordpress/block-library | 1 + .../block-serialization-default-parser | 1 + .../@wordpress/blocks | 1 + .../@wordpress/components | 1 + .../@wordpress/compose | 1 + .../@wordpress/core-data | 1 + .../@wordpress/data | 1 + .../@wordpress/date | 1 + .../@wordpress/deprecated | 1 + .../@wordpress/editor | 1 + .../@wordpress/element | 1 + .../@wordpress/format-library | 1 + .../@wordpress/hooks | 1 + .../@wordpress/html-entities | 1 + .../@wordpress/i18n | 1 + .../@wordpress/is-shallow-equal | 1 + .../@wordpress/keycodes | 1 + .../@wordpress/notices | 1 + .../@wordpress/nux | 1 + .../@wordpress/priority-queue | 1 + .../@wordpress/redux-routine | 1 + .../@wordpress/url | 1 + .../@wordpress/viewport | 1 + .../symlinked-packages/@wordpress/a11y | 1 + .../symlinked-packages/@wordpress/api-fetch | 1 + .../symlinked-packages/@wordpress/autop | 1 + .../symlinked-packages/@wordpress/blob | 1 + .../@wordpress/block-directory | 1 + .../@wordpress/block-editor | 1 + .../@wordpress/block-library | 1 + .../block-serialization-default-parser | 1 + .../symlinked-packages/@wordpress/blocks | 1 + .../symlinked-packages/@wordpress/components | 1 + .../symlinked-packages/@wordpress/compose | 1 + .../symlinked-packages/@wordpress/core-data | 1 + .../symlinked-packages/@wordpress/data | 1 + .../@wordpress/data-controls | 1 + .../symlinked-packages/@wordpress/date | 1 + .../symlinked-packages/@wordpress/deprecated | 1 + .../symlinked-packages/@wordpress/dom | 1 + .../symlinked-packages/@wordpress/edit-post | 1 + .../symlinked-packages/@wordpress/editor | 1 + .../symlinked-packages/@wordpress/element | 1 + .../symlinked-packages/@wordpress/escape-html | 1 + .../@wordpress/format-library | 1 + .../symlinked-packages/@wordpress/hooks | 1 + .../@wordpress/html-entities | 1 + .../symlinked-packages/@wordpress/i18n | 1 + .../@wordpress/is-shallow-equal | 1 + .../@wordpress/keyboard-shortcuts | 1 + .../symlinked-packages/@wordpress/keycodes | 1 + .../symlinked-packages/@wordpress/notices | 1 + .../symlinked-packages/@wordpress/nux | 1 + .../@wordpress/priority-queue | 1 + .../@wordpress/redux-routine | 1 + .../symlinked-packages/@wordpress/rich-text | 1 + .../@wordpress/server-side-render | 1 + .../symlinked-packages/@wordpress/shortcode | 1 + .../symlinked-packages/@wordpress/token-list | 1 + .../symlinked-packages/@wordpress/url | 1 + .../symlinked-packages/@wordpress/viewport | 1 + packages/react-native-editor/yarn.lock | 13872 ++++++++++++++++ 380 files changed, 47122 insertions(+) create mode 100644 packages/react-native-editor/.circleci/config.yml create mode 100644 packages/react-native-editor/.eslintignore create mode 100644 packages/react-native-editor/.eslintrc.js create mode 100644 packages/react-native-editor/.flowconfig create mode 100644 packages/react-native-editor/.gitattributes create mode 100644 packages/react-native-editor/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 packages/react-native-editor/.github/ISSUE_TEMPLATE/feature_request.md create mode 100644 packages/react-native-editor/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 packages/react-native-editor/.gitignore create mode 100644 packages/react-native-editor/.gitmodules create mode 100644 packages/react-native-editor/.prettierignore create mode 100644 packages/react-native-editor/.vscode/extensions.json create mode 100644 packages/react-native-editor/.vscode/launch.json create mode 100644 packages/react-native-editor/.vscode/settings.json create mode 100644 packages/react-native-editor/.watchmanconfig create mode 100644 packages/react-native-editor/DependencyGraph.js.patched create mode 100644 packages/react-native-editor/Gutenberg.podspec create mode 100644 packages/react-native-editor/LICENSE create mode 100644 packages/react-native-editor/README.md create mode 100644 packages/react-native-editor/RELEASE-NOTES.txt create mode 100644 packages/react-native-editor/RNTAztecView.podspec create mode 100644 packages/react-native-editor/__device-tests__/CONTRIBUTING.md create mode 100644 packages/react-native-editor/__device-tests__/README.md create mode 100644 packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion.test.js create mode 100644 packages/react-native-editor/__device-tests__/gutenberg-editor-heading.test.js create mode 100644 packages/react-native-editor/__device-tests__/gutenberg-editor-image.test.js create mode 100644 packages/react-native-editor/__device-tests__/gutenberg-editor-lists-end.test.js create mode 100644 packages/react-native-editor/__device-tests__/gutenberg-editor-lists.test.js create mode 100644 packages/react-native-editor/__device-tests__/gutenberg-editor-paragraph.test.js create mode 100644 packages/react-native-editor/__device-tests__/gutenberg-editor-paste.test.js create mode 100644 packages/react-native-editor/__device-tests__/gutenberg-editor-rotation.test.js create mode 100644 packages/react-native-editor/__device-tests__/helpers/appium-local.js create mode 100644 packages/react-native-editor/__device-tests__/helpers/caps.js create mode 100644 packages/react-native-editor/__device-tests__/helpers/serverConfigs.js create mode 100644 packages/react-native-editor/__device-tests__/helpers/test-data.js create mode 100644 packages/react-native-editor/__device-tests__/helpers/utils.js create mode 100644 packages/react-native-editor/__device-tests__/pages/editor-page.js create mode 100644 packages/react-native-editor/android/app/BUCK create mode 100644 packages/react-native-editor/android/app/build.gradle create mode 100644 packages/react-native-editor/android/app/build_defs.bzl create mode 100644 packages/react-native-editor/android/app/proguard-rules.pro create mode 100644 packages/react-native-editor/android/app/src/debug/AndroidManifest.xml create mode 100644 packages/react-native-editor/android/app/src/debug/res/xml/react_native_config.xml create mode 100644 packages/react-native-editor/android/app/src/main/AndroidManifest.xml create mode 100644 packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainActivity.java create mode 100644 packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java create mode 100644 packages/react-native-editor/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 packages/react-native-editor/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 packages/react-native-editor/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 packages/react-native-editor/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 packages/react-native-editor/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 packages/react-native-editor/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 packages/react-native-editor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 packages/react-native-editor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 packages/react-native-editor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 packages/react-native-editor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 packages/react-native-editor/android/app/src/main/res/values/strings.xml create mode 100644 packages/react-native-editor/android/app/src/main/res/values/styles.xml create mode 100644 packages/react-native-editor/android/build.gradle create mode 100644 packages/react-native-editor/android/gradle.properties create mode 100644 packages/react-native-editor/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 packages/react-native-editor/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 packages/react-native-editor/android/gradlew create mode 100644 packages/react-native-editor/android/gradlew.bat create mode 100644 packages/react-native-editor/android/keystores/BUCK create mode 100644 packages/react-native-editor/android/keystores/debug.keystore.properties create mode 100644 packages/react-native-editor/android/settings.gradle create mode 100644 packages/react-native-editor/app.json create mode 100644 packages/react-native-editor/babel.config.js create mode 100755 packages/react-native-editor/bin/ci-checks-js.sh create mode 100755 packages/react-native-editor/bin/generate-podspecs.sh create mode 100755 packages/react-native-editor/bin/po2android.js create mode 100755 packages/react-native-editor/bin/po2swift.js create mode 100755 packages/react-native-editor/bin/sauce-pre-upload.sh create mode 100755 packages/react-native-editor/bin/update-bintray-repo.sh create mode 100644 packages/react-native-editor/bundle/android/App.js create mode 100644 packages/react-native-editor/bundle/android/App.js.map create mode 100644 packages/react-native-editor/bundle/android/strings.xml create mode 100644 packages/react-native-editor/bundle/ios/App.js create mode 100644 packages/react-native-editor/bundle/ios/App.js.map create mode 100644 packages/react-native-editor/bundle/ios/GutenbergNativeTranslations.swift create mode 100644 packages/react-native-editor/docs/Releasing.md create mode 100644 packages/react-native-editor/extra-node-modules.config.js create mode 160000 packages/react-native-editor/gutenberg create mode 100644 packages/react-native-editor/i18n-cache/.gitignore create mode 100644 packages/react-native-editor/i18n-cache/data/.gitignore create mode 100644 packages/react-native-editor/i18n-cache/index.js create mode 100644 packages/react-native-editor/images/recommended-extensions.png create mode 100644 packages/react-native-editor/index.js create mode 100644 packages/react-native-editor/ios/CustomImageLoader.swift create mode 100644 packages/react-native-editor/ios/gutenberg-Bridging-Header.h create mode 100644 packages/react-native-editor/ios/gutenberg-tvOS/Info.plist create mode 100644 packages/react-native-editor/ios/gutenberg-tvOSTests/Info.plist create mode 100644 packages/react-native-editor/ios/gutenberg.xcodeproj/project.pbxproj create mode 100644 packages/react-native-editor/ios/gutenberg.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 packages/react-native-editor/ios/gutenberg.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/react-native-editor/ios/gutenberg.xcodeproj/xcshareddata/xcschemes/gutenberg-tvOS.xcscheme create mode 100644 packages/react-native-editor/ios/gutenberg.xcodeproj/xcshareddata/xcschemes/gutenberg.xcscheme create mode 100644 packages/react-native-editor/ios/gutenberg/AppDelegate.swift create mode 100644 packages/react-native-editor/ios/gutenberg/Base.lproj/LaunchScreen.xib create mode 100644 packages/react-native-editor/ios/gutenberg/GutenbergViewController.swift create mode 100644 packages/react-native-editor/ios/gutenberg/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packages/react-native-editor/ios/gutenberg/Images.xcassets/Contents.json create mode 100644 packages/react-native-editor/ios/gutenberg/Images.xcassets/aztec.imageset/Contents.json create mode 100644 packages/react-native-editor/ios/gutenberg/Images.xcassets/aztec.imageset/aztec.png create mode 100644 packages/react-native-editor/ios/gutenberg/Info.plist create mode 100644 packages/react-native-editor/ios/gutenberg/MediaPickCoordinator.swift create mode 100644 packages/react-native-editor/ios/gutenberg/MediaProvider.swift create mode 100644 packages/react-native-editor/ios/gutenberg/MediaUploadCoordinator.swift create mode 100644 packages/react-native-editor/ios/gutenbergTests/Info.plist create mode 100644 packages/react-native-editor/ios/gutenbergTests/gutenbergTests.m create mode 100644 packages/react-native-editor/jest.config.js create mode 100644 packages/react-native-editor/jest_gb.config.js create mode 100644 packages/react-native-editor/jest_ui.config.js create mode 100644 packages/react-native-editor/jitpack.yml create mode 100644 packages/react-native-editor/libdefs.js create mode 100644 packages/react-native-editor/package.json create mode 160000 packages/react-native-editor/react-native-aztec-old-submodule create mode 100644 packages/react-native-editor/react-native-aztec/.flowconfig create mode 100644 packages/react-native-editor/react-native-aztec/.gitignore create mode 100644 packages/react-native-editor/react-native-aztec/LICENSE create mode 100644 packages/react-native-editor/react-native-aztec/README.md create mode 100644 packages/react-native-editor/react-native-aztec/android/build.gradle create mode 100644 packages/react-native-editor/react-native-aztec/android/gradle.properties create mode 100644 packages/react-native-editor/react-native-aztec/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 packages/react-native-editor/react-native-aztec/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 packages/react-native-editor/react-native-aztec/android/gradlew create mode 100644 packages/react-native-editor/react-native-aztec/android/gradlew.bat create mode 100644 packages/react-native-editor/react-native-aztec/android/settings.gradle create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/AndroidManifest.xml create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecArrowKeyMovementMethod.java create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecBackSpaceEvent.java create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecBlurEvent.java create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecEndEditingEvent.java create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecEnterEvent.java create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecFocusEvent.java create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecFormattingChangeEvent.java create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecPackage.java create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecPasteEvent.java create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecSelectionChangeEvent.java create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecText.java create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecTextFormatEnum.java create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecTextShadowNode.java create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/kotlin/org/wordpress/mobile/ReactNativeAztec/EnterPressedWatcher.kt create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/res/drawable-hdpi/ic_launcher.png create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/res/drawable-mdpi/ic_launcher.png create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/res/drawable-xhdpi/ic_launcher.png create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/res/drawable-xxhdpi/ic_launcher.png create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/res/values/bools.xml create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/res/values/integers.xml create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/res/values/strings.xml create mode 100644 packages/react-native-editor/react-native-aztec/android/src/main/res/values/template-dimens.xml create mode 100644 packages/react-native-editor/react-native-aztec/example/.babelrc create mode 100644 packages/react-native-editor/react-native-aztec/example/App.js create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/build.gradle create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/AndroidManifest.xml create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/java/com/example/android/MainActivity.java create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/java/com/example/android/MyFragment.java create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/java/com/example/android/common/activities/SampleRNBaseActivity.java create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/res/drawable-hdpi/ic_launcher.png create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/res/drawable-hdpi/tile.9.png create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/res/drawable-mdpi/ic_launcher.png create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/res/drawable-xhdpi/ic_launcher.png create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/res/drawable-xxhdpi/ic_launcher.png create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/res/drawable/ic_reorder_black_24dp.xml create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/res/layout-w720dp/activity_main.xml create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/res/layout/activity_main.xml create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/res/menu/main.xml create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/res/values-sw600dp/template-dimens.xml create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/res/values-sw600dp/template-styles.xml create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/res/values/colors.xml create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/res/values/dimens.xml create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/res/values/fragmentview_strings.xml create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/res/values/strings.xml create mode 100755 packages/react-native-editor/react-native-aztec/example/android/app/src/main/res/values/styles.xml create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/res/values/template-dimens.xml create mode 100644 packages/react-native-editor/react-native-aztec/example/android/app/src/main/res/values/template-styles.xml create mode 100644 packages/react-native-editor/react-native-aztec/example/android/build.gradle create mode 100644 packages/react-native-editor/react-native-aztec/example/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 packages/react-native-editor/react-native-aztec/example/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 packages/react-native-editor/react-native-aztec/example/android/gradlew create mode 100644 packages/react-native-editor/react-native-aztec/example/android/gradlew.bat create mode 100644 packages/react-native-editor/react-native-aztec/example/android/settings.gradle create mode 100644 packages/react-native-editor/react-native-aztec/example/app.json create mode 100644 packages/react-native-editor/react-native-aztec/example/content.js create mode 100644 packages/react-native-editor/react-native-aztec/example/editor.js create mode 100644 packages/react-native-editor/react-native-aztec/example/iOS/example-Bridging-Header.h create mode 100644 packages/react-native-editor/react-native-aztec/example/iOS/example.xcodeproj/project.pbxproj create mode 100644 packages/react-native-editor/react-native-aztec/example/iOS/example.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 packages/react-native-editor/react-native-aztec/example/iOS/example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/react-native-editor/react-native-aztec/example/iOS/example.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/react-native-editor/react-native-aztec/example/iOS/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme create mode 100644 packages/react-native-editor/react-native-aztec/example/iOS/example/AppDelegate.swift create mode 100644 packages/react-native-editor/react-native-aztec/example/iOS/example/Base.lproj/LaunchScreen.xib create mode 100644 packages/react-native-editor/react-native-aztec/example/iOS/example/BridgeDelegate.swift create mode 100644 packages/react-native-editor/react-native-aztec/example/iOS/example/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packages/react-native-editor/react-native-aztec/example/iOS/example/Images.xcassets/Contents.json create mode 100644 packages/react-native-editor/react-native-aztec/example/iOS/example/Images.xcassets/aztec.imageset/Contents.json create mode 100644 packages/react-native-editor/react-native-aztec/example/iOS/example/Images.xcassets/aztec.imageset/aztec.png create mode 100644 packages/react-native-editor/react-native-aztec/example/iOS/example/Info.plist create mode 100644 packages/react-native-editor/react-native-aztec/example/iOS/example/MediaProvider.swift create mode 100644 packages/react-native-editor/react-native-aztec/example/iOS/exampleTests/Info.plist create mode 100644 packages/react-native-editor/react-native-aztec/example/iOS/exampleTests/exampleTests.m create mode 100644 packages/react-native-editor/react-native-aztec/example/index.js create mode 100644 packages/react-native-editor/react-native-aztec/example/package.json create mode 100644 packages/react-native-editor/react-native-aztec/example/rn-cli.config.js create mode 100644 packages/react-native-editor/react-native-aztec/example/yarn.lock create mode 100644 packages/react-native-editor/react-native-aztec/index.js create mode 100644 packages/react-native-editor/react-native-aztec/ios/Cartfile create mode 100644 packages/react-native-editor/react-native-aztec/ios/Cartfile.resolved create mode 100644 packages/react-native-editor/react-native-aztec/ios/RNTAztecView.xcodeproj/project.pbxproj create mode 100644 packages/react-native-editor/react-native-aztec/ios/RNTAztecView.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 packages/react-native-editor/react-native-aztec/ios/RNTAztecView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/react-native-editor/react-native-aztec/ios/RNTAztecView/BlockFormatHandler.swift create mode 100644 packages/react-native-editor/react-native-aztec/ios/RNTAztecView/BlockModel.swift create mode 100644 packages/react-native-editor/react-native-aztec/ios/RNTAztecView/HeadingBlockFormatHandler.swift create mode 100644 packages/react-native-editor/react-native-aztec/ios/RNTAztecView/RCTAztecView-Bridging-Header.h create mode 100644 packages/react-native-editor/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift create mode 100644 packages/react-native-editor/react-native-aztec/ios/RNTAztecView/RCTAztecViewManager.m create mode 100644 packages/react-native-editor/react-native-aztec/ios/RNTAztecView/RCTAztecViewManager.swift create mode 100644 packages/react-native-editor/react-native-aztec/package.json create mode 100644 packages/react-native-editor/react-native-aztec/src/AztecView.js create mode 100644 packages/react-native-editor/react-native-aztec/yarn.lock create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/.gitattributes create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/.gitignore create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/README.md create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/android/build.gradle create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/android/gradle.properties create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 packages/react-native-editor/react-native-gutenberg-bridge/android/gradlew create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/android/gradlew.bat create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/android/settings.gradle create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/android/src/debug/AndroidManifest.xml create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/android/src/main/AndroidManifest.xml create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergBridgeJS2Parent.java create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNReactNativeGutenbergBridgeModule.java create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNReactNativeGutenbergBridgePackage.java create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/Media.java create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/MediaOption.java create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/OkHttpHeaderInterceptor.java create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/RequestExecutor.java create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/index.js create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/ios/Gutenberg.swift create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/ios/GutenbergBridgeDataSource.swift create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/ios/GutenbergBridgeDelegate.swift create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/ios/RNReactNativeGutenbergBridge-Bridging-Header.h create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/ios/RNReactNativeGutenbergBridge.m create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/ios/RNReactNativeGutenbergBridge.swift create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/ios/RNReactNativeGutenbergBridge.xcodeproj/project.pbxproj create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/ios/RNReactNativeGutenbergBridge.xcworkspace/contents.xcworkspacedata create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/package.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/DoubleConversion.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/Folly.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/RNReactNativeRecyclerviewList.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/RNSVG.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-ART.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-Core.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-DevSupport.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-Fabric.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-RCTActionSheet.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-RCTAnimation.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-RCTBlob.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-RCTCameraRoll.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-RCTFabric.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-RCTImage.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-RCTLinking.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-RCTNetwork.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-RCTPushNotification.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-RCTSettings.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-RCTTest.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-RCTText.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-RCTVibration.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-RCTWebSocket.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-cxxreact.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-graphics.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-jscallinvoker.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-jsi.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-jsiexecutor.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-jsinspector.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-turbomodule-core.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React-turbomodule-samples.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/React.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/ReactNativeDarkMode.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/glog.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/react-native-keyboard-aware-scroll-view.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/react-native-safe-area.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/react-native-slider.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/react-native-video.podspec.json create mode 100644 packages/react-native-editor/react-native-gutenberg-bridge/third-party-podspecs/yoga.podspec.json create mode 100644 packages/react-native-editor/resources/fonts/NotoSerif-Bold.ttf create mode 100644 packages/react-native-editor/resources/fonts/NotoSerif-BoldItalic.ttf create mode 100644 packages/react-native-editor/resources/fonts/NotoSerif-Italic.ttf create mode 100644 packages/react-native-editor/resources/fonts/NotoSerif-Regular.ttf create mode 100644 packages/react-native-editor/rn-cli-inside-gb.config.js create mode 100644 packages/react-native-editor/rn-cli.config.js create mode 100644 packages/react-native-editor/sass-transformer-inside-gb.js create mode 100644 packages/react-native-editor/sass-transformer.js create mode 100644 packages/react-native-editor/src/_colors.scss create mode 100644 packages/react-native-editor/src/_native.android.scss create mode 100644 packages/react-native-editor/src/_native.ios.scss create mode 100644 packages/react-native-editor/src/api-fetch-setup.js create mode 100644 packages/react-native-editor/src/globals.js create mode 100644 packages/react-native-editor/src/index.js create mode 100644 packages/react-native-editor/src/index.test.js create mode 100644 packages/react-native-editor/src/initial-html.js create mode 100644 packages/react-native-editor/src/jsdom-patches.js create mode 100644 packages/react-native-editor/src/parser/block-parser-code.test.js create mode 100644 packages/react-native-editor/src/parser/block-parser-more.test.js create mode 100644 packages/react-native-editor/src/parser/block-parser-paragraph.test.js create mode 100644 packages/react-native-editor/src/store/types.js create mode 100644 packages/react-native-editor/src/test/api-fetch-setup.test.js create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/a11y create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/api-fetch create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/autop create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/blob create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/block-library create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/block-serialization-default-parser create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/blocks create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/components create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/compose create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/core-data create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/data create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/date create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/deprecated create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/editor create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/element create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/format-library create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/hooks create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/html-entities create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/i18n create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/is-shallow-equal create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/keycodes create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/notices create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/nux create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/priority-queue create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/redux-routine create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/url create mode 120000 packages/react-native-editor/symlinked-packages-in-parent/@wordpress/viewport create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/a11y create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/api-fetch create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/autop create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/blob create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/block-directory create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/block-editor create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/block-library create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/block-serialization-default-parser create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/blocks create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/components create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/compose create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/core-data create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/data create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/data-controls create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/date create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/deprecated create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/dom create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/edit-post create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/editor create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/element create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/escape-html create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/format-library create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/hooks create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/html-entities create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/i18n create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/is-shallow-equal create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/keyboard-shortcuts create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/keycodes create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/notices create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/nux create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/priority-queue create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/redux-routine create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/rich-text create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/server-side-render create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/shortcode create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/token-list create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/url create mode 120000 packages/react-native-editor/symlinked-packages/@wordpress/viewport create mode 100644 packages/react-native-editor/yarn.lock diff --git a/packages/react-native-editor/.circleci/config.yml b/packages/react-native-editor/.circleci/config.yml new file mode 100644 index 0000000000000..c62fae508113a --- /dev/null +++ b/packages/react-native-editor/.circleci/config.yml @@ -0,0 +1,177 @@ +version: 2.1 + +commands: + yarn-install: + steps: + - restore_cache: + name: Restore Yarn Cache + keys: + - yarn-i18n-v4-{{ .Environment.CIRCLE_JOB }}-{{ checksum "yarn.lock" }} + - run: + name: Yarn Install + command: yarn install --frozen-lockfile --prefer-offline + - save_cache: + name: Save Yarn Cache + key: yarn-i18n-v4-{{ .Environment.CIRCLE_JOB }}-{{ checksum "yarn.lock" }} + paths: + - node_modules + - i18n-cache/data + checkout-gutenberg: + steps: + - run: + name: Checkout Gutenberg + command: git submodule update --init --recursive + add-jest-reporter-dir: + steps: + - run: + name: Create reports directory + command: mkdir reports && mkdir reports/test-results + +jobs: + checks: + parameters: + platform: + type: string + default: "" + check-tests: + type: boolean + default: false + check-correctness: + type: boolean + default: false + docker: + - image: circleci/node:8 + steps: + - checkout + - checkout-gutenberg + - yarn-install + - add-jest-reporter-dir + - run: + name: Set Environment Variables + command: | + echo 'export CHECK_CORRECTNESS=<>' >> $BASH_ENV + echo 'export CHECK_TESTS=<>' >> $BASH_ENV + echo 'export TEST_RN_PLATFORM=<>' >> $BASH_ENV + - run: + name: Run Checks + command: bin/ci-checks-js.sh + environment: + JEST_JUNIT_OUTPUT: "reports/test-results/android-test-results.xml" + - store_test_results: + path: ./reports/test-results + android-device-checks: + docker: + - image: circleci/android:api-29-node + steps: + - checkout + - run: + name: Checkout Gutenberg + command: git submodule update --init --recursive + - yarn-install + - add-jest-reporter-dir + - run: + name: Set Environment Variables + command: | + echo 'export TEST_RN_PLATFORM=android' >> $BASH_ENV + echo 'export TEST_ENV=sauce' >> $BASH_ENV + - run: + name: Bundle Android and Generate debug .apk file for testing + command: yarn test:e2e:build-app:android + - run: + name: Upload apk to sauce labs + command: | + source bin/sauce-pre-upload.sh + curl -u "$SAUCE_USERNAME:$SAUCE_ACCESS_KEY" -X POST -H "Content-Type: application/octet-stream" https://saucelabs.com/rest/v1/storage/automattic/Gutenberg-$SAUCE_FILENAME.apk?overwrite=true --data-binary @./android/app/build/outputs/apk/debug/app-debug.apk + - run: + name: Run Device Tests + command: yarn device-tests + environment: + JEST_JUNIT_OUTPUT: "reports/test-results/android-test-results.xml" + - store_test_results: + path: ./reports/test-results + ios-device-checks: + macos: + xcode: "11.2.1" + steps: + - checkout + - checkout-gutenberg + - yarn-install + - add-jest-reporter-dir + - run: + name: Set Environment Variables + command: | + echo 'export TEST_RN_PLATFORM=ios' >> $BASH_ENV + echo 'export TEST_ENV=sauce' >> $BASH_ENV + - run: + name: Prepare build cache key + command: find yarn.lock ios react-native-aztec/ios react-native-gutenberg-bridge/ios -type f -print0 | sort -z | xargs -0 shasum | tee ios-checksums.txt + - restore_cache: + name: Restore Build Cache + keys: + - ios-build-cache-{{ checksum "ios-checksums.txt" }} + - restore_cache: + name: Restore Dependencies Cache + keys: + - dependencies-v2-{{ checksum "react-native-aztec/ios/Cartfile.resolved" }}-{{ + checksum "yarn.lock" }} + - dependencies-v2-{{ checksum "react-native-aztec/ios/Cartfile.resolved" }} + - dependencies-v2- + - run: + name: Yarn preios (if needed) + command: test -e ios/build/gutenberg/Build/Products/Release-iphonesimulator/gutenberg.app || yarn preios + - save_cache: + name: Save Dependencies Cache + key: dependencies-v2-{{ checksum "react-native-aztec/ios/Cartfile.resolved" }}-{{ + checksum "yarn.lock" }} + paths: + - react-native-aztec/ios/Carthage + - ~/.rncache + - run: + name: Build (if needed) + command: test -e ios/build/gutenberg/Build/Products/Release-iphonesimulator/gutenberg.app || yarn react-native run-ios --configuration Release --no-packager + - run: + name: Bundle iOS + command: yarn test:e2e:bundle:ios + - run: + name: Generate .app file for testing + command: WORK_DIR=$(pwd) && cd ./ios/build/gutenberg/Build/Products/Release-iphonesimulator && zip -r $WORK_DIR/ios/Gutenberg.app.zip gutenberg.app + - run: + name: Upload .app to sauce labs + command: | + source bin/sauce-pre-upload.sh + curl -u "$SAUCE_USERNAME:$SAUCE_ACCESS_KEY" -X POST -H "Content-Type: application/octet-stream" https://saucelabs.com/rest/v1/storage/automattic/Gutenberg-$SAUCE_FILENAME.app.zip?overwrite=true --data-binary @./ios/Gutenberg.app.zip + - run: + name: Run Device Tests + command: | + yarn device-tests + environment: + JEST_JUNIT_OUTPUT: "reports/test-results/ios-test-results.xml" + - store_test_results: + path: ./reports/test-results + - run: + name: Prepare build cache + command: rm ios/build/gutenberg/Build/Products/Release-iphonesimulator/gutenberg.app/main.jsbundle + - save_cache: + name: Save Build Cache + key: ios-build-cache-{{ checksum "ios-checksums.txt" }} + paths: + - ios/build/gutenberg/Build/Products/Release-iphonesimulator/gutenberg.app + +workflows: + gutenberg-mobile: + jobs: + - checks: + name: Check Correctness + check-correctness: true + - checks: + name: Test iOS + platform: ios + check-tests: true + - checks: + name: Test Android + platform: android + check-tests: true + - ios-device-checks: + name: Test iOS on Device + - android-device-checks: + name: Test Android on Device diff --git a/packages/react-native-editor/.eslintignore b/packages/react-native-editor/.eslintignore new file mode 100644 index 0000000000000..7b03f0840bfc5 --- /dev/null +++ b/packages/react-native-editor/.eslintignore @@ -0,0 +1,7 @@ +; ignore the submodules +gutenberg +symlinked-packages +symlinked-packages-in-parent +react-native-aztec +bundle +react-native-aztec-old-submodule diff --git a/packages/react-native-editor/.eslintrc.js b/packages/react-native-editor/.eslintrc.js new file mode 100644 index 0000000000000..05d6c8398516d --- /dev/null +++ b/packages/react-native-editor/.eslintrc.js @@ -0,0 +1,74 @@ +/** + * External dependencies + */ +const { map } = require( 'lodash' ); + +module.exports = { + parser: "babel-eslint", + env: { + browser: true, + "jest/globals": true + }, + globals: { + __DEV__: true + }, + plugins: [ + "react", + "react-native", + "jest", + "flowtype" + ], + extends: [ + "plugin:@wordpress/eslint-plugin/recommended", + "plugin:flowtype/recommended", + ], + settings: { + flowtype: { + onlyFilesWithFlowAnnotation: true, + }, + react: { + pragma: "React", + version: "16.8.3", + flowVersion: "0.92.0", + }, + }, + rules: { + 'no-restricted-syntax': [ + 'error', + // NOTE: We can't include the forward slash in our regex or + // we'll get a `SyntaxError` (Invalid regular expression: \ at end of pattern) + // here. That's why we use \\u002F in the regexes below. + { + selector: 'ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]', + message: 'Path access on WordPress dependencies is not allowed.', + }, + { + selector: 'CallExpression[callee.name=/^(__|_x|_n|_nx)$/] Literal[value=/\\.{3}/]', + message: 'Use ellipsis character (…) in place of three dots', + }, + { + selector: 'ImportDeclaration[source.value="lodash"] Identifier.imported[name="memoize"]', + message: 'Use memize instead of Lodash’s memoize', + }, + { + selector: 'CallExpression[callee.object.name="page"][callee.property.name="waitFor"]', + message: 'Prefer page.waitForSelector instead.', + }, + { + selector: 'JSXAttribute[name.name="id"][value.type="Literal"]', + message: 'Do not use string literals for IDs; use withInstanceId instead.', + }, + { + // Discourage the usage of `Math.random()` as it's a code smell + // for UUID generation, for which we already have a higher-order + // component: `withInstanceId`. + selector: 'CallExpression[callee.object.name="Math"][callee.property.name="random"]', + message: 'Do not use Math.random() to generate unique IDs; use withInstanceId instead. (If you’re not generating unique IDs: ignore this message.)', + }, + { + selector: 'CallExpression[callee.name="withDispatch"] > :function > BlockStatement > :not(VariableDeclaration,ReturnStatement)', + message: 'withDispatch must return an object with consistent keys. Avoid performing logic in `mapDispatchToProps`.', + }, + ], + }, +} diff --git a/packages/react-native-editor/.flowconfig b/packages/react-native-editor/.flowconfig new file mode 100644 index 0000000000000..722fb3f28dd9a --- /dev/null +++ b/packages/react-native-editor/.flowconfig @@ -0,0 +1,115 @@ +[ignore] +; We fork some components by platform +.*/*[.]android.js + +; Ignore templates for 'react-native init' +/node_modules/react-native/local-cli/templates/.* + +; Ignore RN jest +/node_modules/react-native/jest/.* + +; Ignore RNTester +/node_modules/react-native/RNTester/.* + +; Ignore the website subdir +/node_modules/react-native/website/.* + +; Ignore the Dangerfile +/node_modules/react-native/danger/dangerfile.js + +; Ignore Fbemitter +/node_modules/fbemitter/.* + +; Ignore "BUCK" generated dirs +/node_modules/react-native/\.buckd/ + +; Ignore unexpected extra "@providesModule" +.*/node_modules/.*/node_modules/fbjs/.* + +; Ignore polyfills +/node_modules/react-native/Libraries/polyfills/.* + +; Ignore various node_modules +/node_modules/react-native-gesture-handler/.* +/node_modules/expo/.* +/node_modules/react-navigation/.* +/node_modules/xdl/.* +/node_modules/reqwest/.* +/node_modules/metro-bundler/.* +/node_modules/fbjs/.* +/node_modules/graphql/.* +/node_modules/prettier/.* +/node_modules/jsx-to-string/.* +/node_modules/jest-enzyme/.* +/node_modules/enzyme-matchers/.* + +; Ignore react-native-recyclerview-list example app +/node_modules/react-native-recyclerview-list/example + +; Ignore immutable-js. See https://github.com/facebook/immutable-js/issues/1308 +/node_modules/immutable/.* + +; Gutenberg tools +/gutenberg/node_modules/findup/.* +/gutenberg/node_modules/cypress/.* +/gutenberg/node_modules/config-chain/.* +/gutenberg/node_modules/editions/es2015/.* +/gutenberg/node_modules/@parcel/.* + +; Mirror some ignores from Gutenberg tools +/node_modules/config-chain/.* + +; Hack to make Flow works on OS X with a RN project +/node_modules/metro/.* +/node_modules/react-native/.* + +; Ignore the node_modules folders in GB packages +/gutenberg/packages/element/node_modules/.* + +[include] + +[libs] +node_modules/react-native/Libraries/react-native/react-native-interface.js +node_modules/react-native/flow/ +node_modules/expo/flow/ +libdefs.js + +[options] +emoji=true + +module.system=haste +module.system.node.resolve_dirname=node_modules +module.system.node.resolve_dirname=symlinked-packages + +module.file_ext=.js +module.file_ext=.jsx +module.file_ext=.json +module.file_ext=.ios.js +module.file_ext=.scss + +munge_underscores=true + +module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' + +; mock/ignore style files +module.name_mapper='.*\(.scss\)' -> 'empty/object' + +server.max_workers=4 + +suppress_type=$FlowIssue +suppress_type=$FlowFixMe +suppress_type=$FlowFixMeProps +suppress_type=$FlowFixMeState +suppress_type=$FixMe + +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy +suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError + +[untyped] +/node_modules/react-native-keyboard-aware-scroll-view/.* +/node_modules/react-native-safe-area/.* + +[version] +^0.92.0 diff --git a/packages/react-native-editor/.gitattributes b/packages/react-native-editor/.gitattributes new file mode 100644 index 0000000000000..49ff260043bc4 --- /dev/null +++ b/packages/react-native-editor/.gitattributes @@ -0,0 +1 @@ +RELEASE-NOTES.txt merge=union diff --git a/packages/react-native-editor/.github/ISSUE_TEMPLATE/bug_report.md b/packages/react-native-editor/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000000..65bba1987de68 --- /dev/null +++ b/packages/react-native-editor/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6, Pixel 4] + - OS: [e.g. iOS 8.1, Android X] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/packages/react-native-editor/.github/ISSUE_TEMPLATE/feature_request.md b/packages/react-native-editor/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000..bbcbbe7d61558 --- /dev/null +++ b/packages/react-native-editor/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/packages/react-native-editor/.github/PULL_REQUEST_TEMPLATE.md b/packages/react-native-editor/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000000..105a199b831cd --- /dev/null +++ b/packages/react-native-editor/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +Fixes # + +To test: + +PR submission checklist: + +- [ ] I have considered adding unit tests where possible. +- [ ] I have considered if this change warrants user-facing release notes and have added them to `RELEASE-NOTES.txt` if necessary. diff --git a/packages/react-native-editor/.gitignore b/packages/react-native-editor/.gitignore new file mode 100644 index 0000000000000..3002525057798 --- /dev/null +++ b/packages/react-native-editor/.gitignore @@ -0,0 +1,109 @@ +# See https://help.github.com/ignore-files/ for more about ignoring files. + +# expo +.expo/ + +# dependencies +/node_modules + +# misc +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# OS X +.DS_Store + +# Android builds +*.apk +*.ap_ +.gradle/ +android/app/src/main/assets/ + +# iOS builds +*.app.zip + +# files for the dex VM +*.dex + +# Java class files +*.class + +# generated files +gen/ +build/ +build.log + +# Local configuration file (sdk path, etc) +local.properties + +# XCode +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.xcuserstatee + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml + +# node.js +# +node_modules/ +npm-debug.log +yarn-error.log + +# BUCK +buck-out/ +\.buckd/ +*.keystore + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/ + +*/fastlane/report.xml +*/fastlane/Preview.html +*/fastlane/screenshots + +# Bundle artifact +*.jsbundle + +# VSCode local config dir +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +*.pot + +# e2e output log +appium-out.log + +bin/wp-cli.phar + +# Report generated from jest-junit +/junit.xml diff --git a/packages/react-native-editor/.gitmodules b/packages/react-native-editor/.gitmodules new file mode 100644 index 0000000000000..062b5e5a052ec --- /dev/null +++ b/packages/react-native-editor/.gitmodules @@ -0,0 +1,6 @@ +[submodule "gutenberg"] + path = gutenberg + url = ../../WordPress/gutenberg.git +[submodule "react-native-aztec"] + path = react-native-aztec-old-submodule + url = ../react-native-aztec.git diff --git a/packages/react-native-editor/.prettierignore b/packages/react-native-editor/.prettierignore new file mode 100644 index 0000000000000..6247acc281586 --- /dev/null +++ b/packages/react-native-editor/.prettierignore @@ -0,0 +1 @@ +**/gutenberg/ \ No newline at end of file diff --git a/packages/react-native-editor/.vscode/extensions.json b/packages/react-native-editor/.vscode/extensions.json new file mode 100644 index 0000000000000..60a2e524c9eb1 --- /dev/null +++ b/packages/react-native-editor/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "gcazaciuc.vscode-flow-ide", + "msjsdiag.vscode-react-native" + ] +} diff --git a/packages/react-native-editor/.vscode/launch.json b/packages/react-native-editor/.vscode/launch.json new file mode 100644 index 0000000000000..27c9545b33692 --- /dev/null +++ b/packages/react-native-editor/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to packager", + "cwd": "${workspaceFolder}", + "type": "reactnative", + "request": "attach" + }, + ] +} \ No newline at end of file diff --git a/packages/react-native-editor/.vscode/settings.json b/packages/react-native-editor/.vscode/settings.json new file mode 100644 index 0000000000000..9a0d2c44f8850 --- /dev/null +++ b/packages/react-native-editor/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "javascript.validate.enable": false, + + // Enable/disable default JavaScript formatter (For Prettier) + "javascript.format.enable": false, + + // Use 'prettier-eslint' instead of 'prettier'. Other settings will only be fallbacks in case they could not be inferred from eslint rules. + "prettier.eslintIntegration": true, +} \ No newline at end of file diff --git a/packages/react-native-editor/.watchmanconfig b/packages/react-native-editor/.watchmanconfig new file mode 100644 index 0000000000000..0967ef424bce6 --- /dev/null +++ b/packages/react-native-editor/.watchmanconfig @@ -0,0 +1 @@ +{} diff --git a/packages/react-native-editor/DependencyGraph.js.patched b/packages/react-native-editor/DependencyGraph.js.patched new file mode 100644 index 0000000000000..c14fa779d0db9 --- /dev/null +++ b/packages/react-native-editor/DependencyGraph.js.patched @@ -0,0 +1,297 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * + * @format + */ +"use strict"; + +function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { + try { + var info = gen[key](arg); + var value = info.value; + } catch (error) { + reject(error); + return; + } + if (info.done) { + resolve(value); + } else { + Promise.resolve(value).then(_next, _throw); + } +} + +function _asyncToGenerator(fn) { + return function() { + var self = this, + args = arguments; + return new Promise(function(resolve, reject) { + var gen = fn.apply(self, args); + function _next(value) { + asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); + } + function _throw(err) { + asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); + } + _next(undefined); + }); + }; +} + +function _defineProperty(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + return obj; +} + +const AssetResolutionCache = require("./AssetResolutionCache"); + +const DependencyGraphHelpers = require("./DependencyGraph/DependencyGraphHelpers"); + +const JestHasteMap = require("jest-haste-map"); + +const Module = require("./Module"); + +const ModuleCache = require("./ModuleCache"); + +const ResolutionRequest = require("./DependencyGraph/ResolutionRequest"); + +const fs = require("fs"); + +const path = require("path"); + +const _require = require("./DependencyGraph/ModuleResolution"), + ModuleResolver = _require.ModuleResolver; + +const _require2 = require("events"), + EventEmitter = _require2.EventEmitter; + +const _require3 = require("metro-core"), + _require3$Logger = _require3.Logger, + createActionStartEntry = _require3$Logger.createActionStartEntry, + createActionEndEntry = _require3$Logger.createActionEndEntry, + log = _require3$Logger.log; + +const JEST_HASTE_MAP_CACHE_BREAKER = 4; + +class DependencyGraph extends EventEmitter { + constructor(_ref) { + let config = _ref.config, + haste = _ref.haste, + initialHasteFS = _ref.initialHasteFS, + initialModuleMap = _ref.initialModuleMap; + super(); + + _defineProperty(this, "_doesFileExist", filePath => { + return this._hasteFS.exists(filePath); + }); + + this._config = config; + this._assetResolutionCache = new AssetResolutionCache({ + assetExtensions: new Set(config.resolver.assetExts), + getDirFiles: dirPath => fs.readdirSync(dirPath), + platforms: new Set(config.resolver.platforms) + }); + this._haste = haste; + this._hasteFS = initialHasteFS; + this._moduleMap = initialModuleMap; + this._helpers = new DependencyGraphHelpers({ + assetExts: config.resolver.assetExts, + providesModuleNodeModules: config.resolver.providesModuleNodeModules + }); + + this._haste.on("change", this._onHasteChange.bind(this)); + + this._moduleCache = this._createModuleCache(); + + this._createModuleResolver(); + } + + static _createHaste(config) { + return new JestHasteMap({ + computeDependencies: false, + computeSha1: true, + extensions: config.resolver.sourceExts.concat(config.resolver.assetExts), + forceNodeFilesystemAPI: !config.resolver.useWatchman, + hasteImplModulePath: config.resolver.hasteImplModulePath, + ignorePattern: config.resolver.blacklistRE || / ^/, + mapper: config.resolver.virtualMapper, + maxWorkers: config.maxWorkers, + mocksPattern: "", + name: "metro-" + JEST_HASTE_MAP_CACHE_BREAKER, + platforms: config.resolver.platforms, + providesModuleNodeModules: config.resolver.providesModuleNodeModules, + retainAllFiles: true, + resetCache: config.resetCache, + rootDir: config.projectRoot, + roots: config.watchFolders, + throwOnModuleCollision: true, + useWatchman: config.resolver.useWatchman, + watch: false + }); + } + + static load(config) { + return _asyncToGenerator(function*() { + const initializingMetroLogEntry = log( + createActionStartEntry("Initializing Metro") + ); + config.reporter.update({ + type: "dep_graph_loading" + }); + + const haste = DependencyGraph._createHaste(config); + + const _ref2 = yield haste.build(), + hasteFS = _ref2.hasteFS, + moduleMap = _ref2.moduleMap; + + log(createActionEndEntry(initializingMetroLogEntry)); + config.reporter.update({ + type: "dep_graph_loaded" + }); + return new DependencyGraph({ + haste, + initialHasteFS: hasteFS, + initialModuleMap: moduleMap, + config + }); + })(); + } + + _getClosestPackage(filePath) { + const parsedPath = path.parse(filePath); + const root = parsedPath.root; + let dir = parsedPath.dir; + + do { + const candidate = path.join(dir, "package.json"); + + if (this._hasteFS.exists(candidate)) { + return candidate; + } + + dir = path.dirname(dir); + } while (dir !== "." && dir !== root); + + return null; + } + + _onHasteChange(_ref3) { + let eventsQueue = _ref3.eventsQueue, + hasteFS = _ref3.hasteFS, + moduleMap = _ref3.moduleMap; + this._hasteFS = hasteFS; + + this._assetResolutionCache.clear(); + + this._moduleMap = moduleMap; + eventsQueue.forEach(_ref4 => { + let type = _ref4.type, + filePath = _ref4.filePath; + return this._moduleCache.processFileChange(type, filePath); + }); + + this._createModuleResolver(); + + this.emit("change"); + } + + _createModuleResolver() { + this._moduleResolver = new ModuleResolver({ + allowPnp: this._config.resolver.allowPnp, + dirExists: filePath => { + try { + return fs.lstatSync(filePath).isDirectory(); + } catch (e) {} + + return false; + }, + doesFileExist: this._doesFileExist, + extraNodeModules: this._config.resolver.extraNodeModules, + isAssetFile: filePath => this._helpers.isAssetFile(filePath), + mainFields: this._config.resolver.resolverMainFields, + moduleCache: this._moduleCache, + moduleMap: this._moduleMap, + preferNativePlatform: true, + resolveAsset: (dirPath, assetName, platform) => + this._assetResolutionCache.resolve(dirPath, assetName, platform), + resolveRequest: this._config.resolver.resolveRequest, + sourceExts: this._config.resolver.sourceExts + }); + } + + _createModuleCache() { + return new ModuleCache({ + getClosestPackage: this._getClosestPackage.bind(this) + }); + } + + getSha1(filename) { + // TODO If it looks like we're trying to get the sha1 from a file located + // within a Zip archive, then we instead compute the sha1 for what looks + // like the Zip archive itself. + const splitIndex = filename.indexOf(".zip/"); + const containerName = + splitIndex !== -1 ? filename.slice(0, splitIndex + 4) : filename; // TODO Calling realpath allows us to get a hash for a given path even when + // it's a symlink to a file, which prevents Metro from crashing in such a + // case. However, it doesn't allow Metro to track changes to the target file + // of the symlink. We should fix this by implementing a symlink map into + // Metro (or maybe by implementing those "extra transformation sources" we've + // been talking about for stuff like CSS or WASM). + + const resolvedPath = fs.realpathSync(containerName); + + const sha1 = this._hasteFS.getSha1(resolvedPath); + + if (!sha1) { + throw new ReferenceError( + `SHA-1 for file ${filename} (${resolvedPath}) is not computed` + ); + } + + return sha1; + } + + getWatcher() { + return this._haste; + } + + end() { + this._haste.end(); + } + + resolveDependency(from, to, platform) { + const req = new ResolutionRequest({ + moduleResolver: this._moduleResolver, + entryPath: from, + helpers: this._helpers, + platform: platform || null, + moduleCache: this._moduleCache + }); + return req.resolveDependency(this._moduleCache.getModule(from), to).path; + } + + getHasteName(filePath) { + const hasteName = this._hasteFS.getModuleName(filePath); + + if (hasteName) { + return hasteName; + } + + return path.relative(this._config.projectRoot, filePath); + } +} + +module.exports = DependencyGraph; diff --git a/packages/react-native-editor/Gutenberg.podspec b/packages/react-native-editor/Gutenberg.podspec new file mode 100644 index 0000000000000..bd6db8bbb9189 --- /dev/null +++ b/packages/react-native-editor/Gutenberg.podspec @@ -0,0 +1,26 @@ +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) +# Use the same RN version that the JS tools use +react_native_version = package['dependencies']['react-native'] +# Extract the tagged version if package.json points to a tag +react_native_version = react_native_version.split("#v").last if react_native_version.include? "#v" + +Pod::Spec.new do |s| + s.name = 'Gutenberg' + s.version = package['version'] + s.summary = 'Printing since 1440' + s.homepage = 'https://github.com/wordpress-mobile/gutenberg-mobile' + s.license = package['license'] + s.authors = 'Automattic' + s.platform = :ios, '11.0' + s.source = { :git => 'https://github.com/wordpress-mobile/gutenberg-mobile.git' } + s.source_files = 'react-native-gutenberg-bridge/ios/*.{h,m,swift}' + s.requires_arc = true + s.preserve_paths = 'bundle/ios/*' + s.swift_version = '5.0' + + s.dependency 'React', react_native_version + s.dependency 'React-RCTImage', react_native_version + + s.dependency 'WordPress-Aztec-iOS' + s.dependency 'RNTAztecView' +end diff --git a/packages/react-native-editor/LICENSE b/packages/react-native-editor/LICENSE new file mode 100644 index 0000000000000..d159169d10508 --- /dev/null +++ b/packages/react-native-editor/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/packages/react-native-editor/README.md b/packages/react-native-editor/README.md new file mode 100644 index 0000000000000..762f601ac811f --- /dev/null +++ b/packages/react-native-editor/README.md @@ -0,0 +1,168 @@ +# Mobile Gutenberg + +This is the mobile version of [Gutenberg](https://github.com/WordPress/gutenberg), targeting Android and iOS. It's a React Native library bootstrapped by CRNA and now ejected. + +## Getting Started + +### Prerequisites + +For a developer experience closer to the one the project maintainers current have, make sure you have the following tools installed: + +* git +* [nvm](https://github.com/creationix/nvm) +* Node.js and npm (use nvm to install them) +* yarn (`npm install -g yarn`) +* [AndroidStudio](https://developer.android.com/studio/) to be able to compile the Android version of the app +* [Xcode](https://developer.apple.com/xcode/) to be able to compile the iOS app +* [Carthage](https://github.com/Carthage/Carthage#installing-carthage) needed for fetching the Aztec dependency. + + +Note that the OS platform used by the maintainers is macOS but the tools and setup should be usable in other platforms too. + +### Clone the project + +* Clone the project and submodules: +``` +git clone --recurse-submodules https://github.com/wordpress-mobile/gutenberg-mobile.git +``` + +* Or if you already have the project cloned, initialize and update the submodules: +``` +git submodule init +git submodule update +``` + +## Set up + +Before running the demo app, you need to download and install the project dependencies. This is done via the following command: + +``` +nvm install --latest-npm +yarn install +``` + +## Run + +``` +yarn start +``` + +Runs the packager (Metro) in development mode. The packager stays running to serve the app bundle to the clients that request it. + +With the packager running, open another terminal window and use the following command to compile and run the Android app: + +``` +yarn android +``` + +The app should now open in a connected device or a running emulator and fetch the JavaScript code from the running packager. + +To compile and run the iOS variant of the app using the _default_ simulator device, use: + +``` +yarn ios +``` + +which will attempt to open your app in the iOS Simulator if you're on a Mac and have it installed. + +### Running on Other iOS Device Simulators + +To compile and run the app using a different device simulator, use: + +``` +yarn ios --simulator="DEVICE_NAME" +``` + +For example, if you'd like to run in an iPhone Xs Max, try: + +``` +yarn ios --simulator="iPhone Xs Max" +``` + +To see a list of all of your available iOS devices, use `xcrun simctl list devices`. + +### When things seem crazy + +Some times, and especially when tweaking anything in the `package.json`, Babel configuration (`.babelrc`) or the Jest configuration (`jest.config.js`), your changes might seem to not take effect as expected. On those times, you might need to clean various caches before starting the packager. To do that, run the script: `yarn start:reset`. Other times, you might want to reinstall the NPM packages from scratch and the `yarn clean:install` script can be handy. + +## Developing with Visual Studio Code + +Although you're not required to use Visual Studio Code for developing gutenberg-mobile, it is the recommended IDE and we have some configuration for it. + +When you first open the project in Visual Studio, you will be prompted to install some recommended extensions. This will help with some things like type checking and debugging. + +![Prompt to install recommended extensions](images/recommended-extensions.png) + +One of the extensions we are using is the [React Native Tools](https://marketplace.visualstudio.com/items?itemName=vsmobile.vscode-react-native). This allows you to run the packager from VSCode or launch the application on iOS or Android. It also adds some debug configurations so you can set breakpoints and debug the application directly from VSCode. Take a look at the [extension documentation](https://marketplace.visualstudio.com/items?itemName=vsmobile.vscode-react-native) for more details. + +## Unit Tests + +Use the following command to run the test suite: + +``` +yarn test +``` + +It will run the [jest](https://github.com/facebook/jest) test runner on your tests. The tests are running on the desktop against Node.js. + +To run the tests with debugger support, start it with the following CLI command: + +``` +yarn test:debug +``` + +Then, open `chrome://inspect` in Chrome to attach the debugger (look into the "Remote Target" section). While testing/developing, feel free to springle `debugger` statements anywhere in the code that you'd like the debugger to break. + +## Writing and Running Unit Tests + +This project is set up to use [jest](https://facebook.github.io/jest/) for tests. You can configure whatever testing strategy you like, but jest works out of the box. Create test files in directories called `__tests__` or with the `.test.js` extension to have the files loaded by jest. See an example test [here](https://github.com/wordpress-mobile/gutenberg-mobile/blob/develop/src/app/App.test.js). The [jest documentation](https://facebook.github.io/jest/docs/en/getting-started.html) is also a wonderful resource, as is the [React Native testing tutorial](https://facebook.github.io/jest/docs/en/tutorial-react-native.html). + +## UI Tests + +This repository uses Appium to run UI tests. The tests live in `__device-tests__` and are written using Appium to run tests against simulators and real devices. To run these you'll need to check off a few things: + +* For now when running the tests you'll need to ensure the metro bundler is not running. +* [Appium cli](https://github.com/appium/appium/blob/master/docs/en/about-appium/getting-started.md) installed and available globally, I'd also recommend using [appium doctor](https://github.com/appium/appium-doctor) to ensure all of Appium's dependencies are good to go. You don't have to worry about starting the server yourself, the tests handle starting the server on port 4723, just be sure that the port is free or feel free to change the port number in the test file. +* For iOS a simulator should automatically launch but for Android you'll need to have an emulator *with at least platform version 8.0* fired up and running. + +After those are checked off to run the UI tests on iOS run + +`yarn test:e2e:ios:local` + +and for android run, + +`yarn test:e2e:android:local` + +Note, you might experience problems that seem to be related to the tests starting the Appium server, for example errors that say `Connection Refused`, `Connection Reset` or `The requested environment is not available`. Sorry about that this is still a WIP, you can manually start the Appium server via [appium desktop](https://github.com/appium/appium-desktop) or the cli, then change the port number in the tests while optionally commenting out related code in the `beforeAll` and `afterAll` block. + +For a more detailed outline of the UI tests and how to get started writing one please visit the [UI Test documentation](https://github.com/wordpress-mobile/gutenberg-mobile/blob/develop/__device-tests__/README.md) and our [contributing guide](https://github.com/wordpress-mobile/gutenberg-mobile/blob/develop/__device-tests__/CONTRIBUTING.md). + +## Static analysis and code style + +The project includes a linter (`eslint`) to perform codestyle and static analysis of the code. The configuration used is the same as [the one in the Gutenberg project](https://github.com/WordPress/gutenberg/blob/master/eslint/config.js). To perform the check, run: + +``` +yarn lint +``` + +To have the linter also _fix_ the violations run: `yarn lint:fix`. + +In parallel to `eslint` the project uses `Prettier` for codestyling. Run: + +``` +yarn prettier +``` +to enforce the style. This will modify the source files to make them conform to the rules. + +`Flow` is used as a static type checker for JavaScript code. Flow checks JavaScript code for errors through static type annotations. These types allow you to tell Flow how you want your code to work, and Flow will make sure it does work that way. To perform the check run: + +``` +yarn flow +``` + +You might want to use Visual Studio Code as an editor. The project includes the configuration needed to use the above codestyle and lint tools automatically. + +## License + +Gutenberg Mobile is an Open Source project covered by the [GNU General Public License version 2](LICENSE). + diff --git a/packages/react-native-editor/RELEASE-NOTES.txt b/packages/react-native-editor/RELEASE-NOTES.txt new file mode 100644 index 0000000000000..45937ac9041a9 --- /dev/null +++ b/packages/react-native-editor/RELEASE-NOTES.txt @@ -0,0 +1,102 @@ +1.20.0 +------ +* Fix bug where image placeholders would sometimes not be shown + +1.19.0 +------ +* Add support for changing Settings in List Block. +* [iOS] Fix crash dismissing bottom-sheet after device rotation. +* [Android] Add support for Preformatted block. +* New block: Gallery. You can now create image galleries using WordPress Media library. Upload feature is coming soon. +* Add support for Video block settings + +1.18.0 +------ +* [iOS] Added native fullscreen preview when clicking image from Image Block +* New block: Spacer + +1.17.0 +------ +* Include block title in Unsupported block's UI +* Show new-block-indicator when no blocks at all and when at the last block +* Use existing links in the clipboard to prefill url field when inserting new link. +* Media & Text block alignment options +* Add alignment controls for paragraph blocks +* [iOS] Fix issue where the keyboard would not capitalize sentences correctly on some cases. +* [iOS] Support for Pexels image library +* [Android] Added native fullscreen preview when clicking image from Image Block +* [iOS] Add support for Preformatted block. +* [Android] Fix issue when removing image/page break block crashes the app + +1.16.1 +------ +* [iOS] Fix tap on links bug that reappear on iOS 13.2 + +1.16.0 +------ +* [Android] Add support for pexels images +* Add left, center, and right image alignment controls +1.15.3 +------ +* [iOS] Fix a layout bug in RCTAztecView in iOS 13.2 + +1.15.2 +------ +* Fix issue when copy/paste photos from other apps, was not inserting an image on the post. +* Fix issue where the block inserter layout wasn't correct after device rotation. + +1.15.0 +------ +* Fix issue when multiple media selection adds only one image or video block on Android +* Fix issue when force Touch app shortcut doesn't work properly selecting "New Photo Post" on iOS +* Add Link Target (Open in new tab) to Image Block. +* [iOS] DarkMode improvements. +* [iOS] Update to iOS 11 and Swift 5 +* New block: Media & Text + +1.14.0 +------ +* Fix a bug on iOS 13.0 were tapping on a link opens Safari +* Fix a link editing issue, where trying to add a empty link at the start of another link would remove the existing link. +* Fix missing content on long posts in html mode on Android + +1.12.0 +------ +* Add rich text styling to video captions +* Prevent keyboard dismissal when switching between caption and text block on Android +* Blocks that would be replaced are now hidden when add block bottom sheet displays +* Tapping on empty editor area now always inserts new block at end of post + +1.11.0 +------ +* Toolbar scroll position now resets when its content changes. +* Dark Mode for iOS. + +1.10.0 +------ +* Adding a block from the post title now shows the add block here indicator. +* Deselect post title any time a block is added +* Fix loss of center alignment in image captions on Android + +1.9.0 +------ +* Enable video block on Android platform +* Tapping on an empty editor area will create a new paragraph block +* Fix content loss issue when loading unsupported blocks containing inner blocks. +* Adding a block from the Post Title now inserts the block at the top of the Post. + +1.8.0 +------ +* Fix pasting simple text on Post Title +* Remove editable empty line after list on the List block +* Performance improvements on rich text editing + +1.7.0 +------ +* Fixed keyboard flickering issue after pressing Enter repeatedly on the Post Title. +* New blocks are available: video/quote/more + +1.6.0 +------ +* Fixed issue with link settings where “Open in New Tab” was always OFF on open. +* Added UI to display a warning when a block has invalid content. diff --git a/packages/react-native-editor/RNTAztecView.podspec b/packages/react-native-editor/RNTAztecView.podspec new file mode 100644 index 0000000000000..2ea3d382465f2 --- /dev/null +++ b/packages/react-native-editor/RNTAztecView.podspec @@ -0,0 +1,23 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) + +Pod::Spec.new do |s| + s.name = 'RNTAztecView' + s.version = package['version'] + s.summary = 'Aztec editor for React Native' + s.license = package['license'] + s.homepage = 'https://github.com/wordpress-mobile/gutenberg-mobile' + s.authors = 'Automattic' + s.source = { :git => 'https://github.com/wordpress-mobile/gutenberg-mobile.git' } + s.source_files = 'react-native-aztec/ios/RNTAztecView/*.{h,m,swift}' + s.public_header_files = 'react-native-aztec/ios/RNTAztecView/*.h' + s.requires_arc = true + s.platforms = { :ios => "11.0" } + s.swift_version = '5.0' + s.xcconfig = {'OTHER_LDFLAGS' => '-lxml2', + 'HEADER_SEARCH_PATHS' => '/usr/include/libxml2'} + s.dependency 'React-Core' + s.dependency 'WordPress-Aztec-iOS' + +end diff --git a/packages/react-native-editor/__device-tests__/CONTRIBUTING.md b/packages/react-native-editor/__device-tests__/CONTRIBUTING.md new file mode 100644 index 0000000000000..057cbfc84a36d --- /dev/null +++ b/packages/react-native-editor/__device-tests__/CONTRIBUTING.md @@ -0,0 +1,44 @@ +Writing a UI test? Great! 😬 This guide is here to help fill in some of the blanks of how the tests are written now and how you can add a new one. + +You can find our on-device UI tests in the `__device-tests__` folder and that's where all of the code for that really lives. +The test suite follows a sort of [Page Object Pattern](https://webdriver.io/docs/pageobjects.html), the `__device-tests__/pages/editor-page.js` manages all interactions with the pages and the `__device-tests__/gutenberg-editor.test.js` actually uses the functions made available via the Page Object `EditorPage` to drive the test cases. At the time of writing this, all the tests live there but as the suite gets large it might be better to manage different classes of tests in different files. + +So what does the process for writing a test look like? Here are some steps that I hope can help make this easier, + +### First, define the scenario + +- What are the actions that need to take place here? Walk through the scenario and manually to have an idea of what the test steps will need to do, the elements you'll need to interact with and how you're going to need to interact with them. I found it helps to properly define the steps taken in the scenario and the different user interactions that are needed to accomplish it. + +You'd just add a new scenario to the test file as well that would look something like, + +```javscript +it( 'should be able to do something', async () => { + // Code to do something... +} ); +``` + +That first parameter in the block above is where you'd put a short description of the scenario while the next parameter is the code you'd like to execute. + +### Second, figure out how to find the elements + +- The UI tests rely on locator strategies to identify elements... There's a number of locator strategies available to use and [this blog post](https://saucelabs.com/blog/advanced-locator-strategies) describes in a little more detail what a few of these are and how to use them. You'll need to start thinking about what locator strategy you'll need to use to find the elements you need if it isn't already available. +- The preferred strategy is the accessibility identifier and in a lot of cases this might not be possible and you'll have to resort to other less robust alternatives such as XPath. + +There's a few tools you have available to figure out what you need. + +For Android, you can fire up the app and then within Android Studio select `Tools -> Layout Inspector` which will then open up a `.li` file which you can then use to inspect various areas of the app, here's a [screenshot](https://d.pr/free/i/anU50R) of what that looks like. + +For iOS, you can also fire up and use the accessibility inspector, which is an app that should come available on your OSX machine. From there you can choose the process running your simulator and inspect various areas of the app. + +Alternative for both of these platforms and for an interface to simulate the commands I'd recommend [Appium Desktop](https://github.com/appium/appium-desktop/releases/tag/v1.12.1). A great tool for inspecting the view hierarchy and interacting with elements on screen as your test would. Here's a [screenshot](https://d.pr/free/i/GziQ5Q) of what that would look like. + +Using one or a combination of these tools will make it much easier to identify what locator strategy you're going to use or which elements need accessibility identifiers to ease the search process without affecting VoiceOver features. + +### Finally, once you've figured out how you're going to find the elements + +- You'll write any functions needed to interact with the page in the `EditorPage` page object and then call those interactions within the test. The code you'll need to write to actually do the finding will use a combination of + +- Appium's spec http://appium.io/docs/en/about-appium/intro/ which you can find examples of a variety of functions under the commands tab +- WebDriver I/O Appium protocols https://webdriver.io/docs/api/appium.html which provides examples and descriptions of what those look like. + +It takes some getting use to but looking at the existing code should be helpful in identifying common commands that it'd help to be familiar with. diff --git a/packages/react-native-editor/__device-tests__/README.md b/packages/react-native-editor/__device-tests__/README.md new file mode 100644 index 0000000000000..28bead00f73fe --- /dev/null +++ b/packages/react-native-editor/__device-tests__/README.md @@ -0,0 +1,56 @@ +# Overview + +We use [appium](http://appium.io/) combined with [SauceLabs](https://saucelabs.com/) as an on-device testing solution for covering writing flows using Gutenberg blocks. + +Appium is built on the idea that testing native apps shouldn't require including an SDK or recompiling your app. And that you should be able to use your preferred test practices, frameworks, and tools. Appium is an open source project and has made design and tool decisions to encourage a vibrant contributing community. + +SauceLabs is a cloud hosting platform that provides access to a variety of simulators, emulators and real devices. + +## Getting set up to run the tests + +### Emulators && Simulators + +iOS: Once you've already set up XCode and the simulators you should be good to go to run the tests on an iOS simulator. + +Android: You'll need to have created the emulator images and fired up the desired emulator before running the tests against an Android emulator. + +### Real Devices + +TBA + +## Running the tests locally + +TLDR; to run the tests locally ensure metro isn't running and then run `yarn test:e2e:ios:local` and `yarn test:e2e:android:local` for the desired platform. + +Those commands include the process to build a testable version of the app, if it's the case you don't want to run the +full suite and want to run a specific file or files you can use the e2e build commands that can be found in the package.json for the respective platform and then +run `TEST_RN_PLATFORM=android yarn device-tests ` where the pattern can just be the file path. + +### Starting the Appium Server + +One of the Caveats to using Appium is the need for the Appium server to be running to interact with the Simulator or Device through Webdriver, as a result the appium server will need to be started before running the tests. To make the entire process easier in the `beforeAll` block of the tests an Appium instance is fired up on a default port of 4723. If you already have something running on that port and would rather not stop that you can change the port within the code that starts that up. At the moment that port number is referenced from the config located at `__device-tests__/helpers/serverConfigs.js`. The process is killed in the `afterAll` block but at the time of writing this there's a small chance some errors might cause it not to get there so it might be best to kill the process yourself if you think something is up. The server output when running the tests are written to `appium-out.log`, this can provide useful information when debugging the issues with the tests. + +### WebDriver capabilities + +Appium uses a config object that contains `capabilities` to define how it will connect to a simulator or device, this object is currently located in `__device-tests__/helpers/caps.js` and then referenced when firing up the driver. There are two values that I think are important to know and that's + +- `platformVersion` which is the platform version of a connected adb device. e.g `9.0` for Android or `12.2` for iOS. The version used here is upper bounded by the max allowed on CI but feel free to change this value locally as needed. +- `app` which is the absolute path to the `.app` or `.apk` file or the path relative to the **Appium root**. It's important to note that that when using the relative paths it's not to the project folder but to the appium server, since by default we start up appium in the project root when running the paths appear relative to the root but if you were using another instance of the Appium server the relative path would need to come from there. + +A full spec on the capabilities can be found [here](http://appium.io/docs/en/writing-running-appium/caps/). If you'd like to change configurations like +what port appium runs on or what device or emulator the tests should be executed on that file would be where you'd like to make that update. + +## The run process + +At the moment when running locally, the app attempts to fire up an appium server and then connects to it via webdriver. Then + +* on Android, a debug version of the app is bundled, built, and used. +* on iOS a release version is bundled built and used. + +**It's important to ensure that **metro is not running.** This would cause the value of the `__DEV__` variable to be true and load up the sample blocks.** + +After the build is complete, an appium server is fired up on port 4723 and the device tests are ran on the connected device/simulator. + +----- + +To read more about writing your own tests please read the [contributing guide](https://github.com/wordpress-mobile/gutenberg-mobile/blob/develop/__device-tests__/CONTRIBUTING.md) diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion.test.js new file mode 100644 index 0000000000000..d4434bbaa8cda --- /dev/null +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion.test.js @@ -0,0 +1,124 @@ +/** + * @format + * */ + +/** + * Internal dependencies + */ +import EditorPage from './pages/editor-page'; +import { + setupDriver, + isLocalEnvironment, + stopDriver, + isAndroid, + swipeDown, + clickMiddleOfElement, +} from './helpers/utils'; +import testData from './helpers/test-data'; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000000; + +describe( 'Gutenberg Editor tests for Block insertion', () => { + let driver; + let editorPage; + let allPassed = true; + + // Use reporter for setting status for saucelabs Job + if ( ! isLocalEnvironment() ) { + const reporter = { + specDone: async ( result ) => { + allPassed = allPassed && result.status !== 'failed'; + }, + }; + + jasmine.getEnv().addReporter( reporter ); + } + + beforeAll( async () => { + driver = await setupDriver(); + editorPage = new EditorPage( driver ); + } ); + + it( 'should be able to see visual editor', async () => { + // wait for the block editor to load + await expect( editorPage.getBlockList() ).resolves.toBe( true ); + } ); + + it( 'should be able to insert block into post', async () => { + await editorPage.addNewParagraphBlock(); + let paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 1 ); + if ( isAndroid() ) { + await paragraphBlockElement.click(); + } + await editorPage.sendTextToParagraphBlockAtPosition( 1, testData.longText ); + // Should have 3 paragraph blocks at this point + + paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 2 ); + await paragraphBlockElement.click(); + + await editorPage.addNewParagraphBlock(); + paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 3 ); + await paragraphBlockElement.click(); + await editorPage.sendTextToParagraphBlockAtPosition( 3, testData.mediumText ); + + await editorPage.verifyHtmlContent( testData.blockInsertionHtml ); + + // wait for the block editor to load and for accessibility ids to update + await driver.sleep( 3000 ); + + // Workaround for now since deleting the first element causes a crash on CI for Android + if ( isAndroid() ) { + paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 3, { autoscroll: true } ); + await paragraphBlockElement.click(); + await editorPage.removeParagraphBlockAtPosition( 3 ); + for ( let i = 3; i > 0; i-- ) { + // wait for accessibility ids to update + await driver.sleep( 1000 ); + paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( i, { autoscroll: true } ); + await paragraphBlockElement.click(); + await editorPage.removeParagraphBlockAtPosition( i ); + } + } else { + for ( let i = 4; i > 0; i-- ) { + // wait for accessibility ids to update + await driver.sleep( 1000 ); + paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 1 ); + await clickMiddleOfElement( driver, paragraphBlockElement ); + await editorPage.removeParagraphBlockAtPosition( 1 ); + } + } + } ); + + it( 'should be able to insert block at the beginning of post from the title', async () => { + await editorPage.addNewParagraphBlock(); + let paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 1 ); + if ( isAndroid() ) { + await paragraphBlockElement.click(); + } + await editorPage.sendTextToParagraphBlockAtPosition( 1, testData.longText ); + // Should have 3 paragraph blocks at this point + + if ( isAndroid() ) { + await editorPage.dismissKeyboard(); + } + + await swipeDown( driver ); + const titleElement = await editorPage.getTitleElement( { autoscroll: true } ); + await titleElement.click(); + await titleElement.click(); + + await editorPage.addNewParagraphBlock(); + paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 1 ); + await clickMiddleOfElement( driver, paragraphBlockElement ); + await editorPage.sendTextToParagraphBlockAtPosition( 1, testData.mediumText ); + await paragraphBlockElement.click(); + await editorPage.verifyHtmlContent( testData.blockInsertionHtmlFromTitle ); + } ); + + afterAll( async () => { + if ( ! isLocalEnvironment() ) { + driver.sauceJobStatus( allPassed ); + } + await stopDriver( driver ); + } ); +} ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-heading.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-heading.test.js new file mode 100644 index 0000000000000..c3d62168c42b2 --- /dev/null +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-heading.test.js @@ -0,0 +1,76 @@ +/** + * @format + * */ + +/** + * Internal dependencies + */ +import EditorPage from './pages/editor-page'; +import { + setupDriver, + isLocalEnvironment, + stopDriver, + isAndroid, +} from './helpers/utils'; +import testData from './helpers/test-data'; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000000; + +describe( 'Gutenberg Editor tests', () => { + let driver; + let editorPage; + let allPassed = true; + + // Use reporter for setting status for saucelabs Job + if ( ! isLocalEnvironment() ) { + const reporter = { + specDone: async ( result ) => { + allPassed = allPassed && result.status !== 'failed'; + }, + }; + + jasmine.getEnv().addReporter( reporter ); + } + + beforeAll( async () => { + driver = await setupDriver(); + editorPage = new EditorPage( driver ); + } ); + + it( 'should be able to see visual editor', async () => { + await expect( editorPage.getBlockList() ).resolves.toBe( true ); + } ); + + it( 'should be able to create a post with heading and paragraph blocks', async () => { + await editorPage.addNewHeadingBlock(); + let headingBlockElement = await editorPage.getHeadingBlockAtPosition( 1 ); + + if ( isAndroid() ) { + await headingBlockElement.click(); + } + await editorPage.sendTextToHeadingBlock( headingBlockElement, testData.heading ); + + await editorPage.addNewParagraphBlock(); + let paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 2 ); + await editorPage.sendTextToParagraphBlock( paragraphBlockElement, testData.mediumText ); + + await editorPage.addNewParagraphBlock(); + paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 3 ); + await editorPage.sendTextToParagraphBlock( paragraphBlockElement, testData.mediumText ); + + await editorPage.addNewHeadingBlock(); + headingBlockElement = await editorPage.getHeadingBlockAtPosition( 4 ); + await editorPage.sendTextToHeadingBlock( headingBlockElement, testData.heading ); + + await editorPage.addNewParagraphBlock(); + paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 5 ); + await editorPage.sendTextToParagraphBlock( paragraphBlockElement, testData.mediumText ); + } ); + + afterAll( async () => { + if ( ! isLocalEnvironment() ) { + driver.sauceJobStatus( allPassed ); + } + await stopDriver( driver ); + } ); +} ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-image.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-image.test.js new file mode 100644 index 0000000000000..e50480715653f --- /dev/null +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-image.test.js @@ -0,0 +1,107 @@ +/** + * @format + * */ + +/** + * Internal dependencies + */ +import EditorPage from './pages/editor-page'; +import { + setupDriver, + isLocalEnvironment, + stopDriver, + isAndroid, + clickMiddleOfElement, + swipeUp, +} from './helpers/utils'; +import testData from './helpers/test-data'; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000000; + +describe( 'Gutenberg Editor Image Block tests', () => { + let driver; + let editorPage; + let allPassed = true; + + // Use reporter for setting status for saucelabs Job + if ( ! isLocalEnvironment() ) { + const reporter = { + specDone: async ( result ) => { + allPassed = allPassed && result.status !== 'failed'; + }, + }; + + jasmine.getEnv().addReporter( reporter ); + } + + beforeAll( async () => { + driver = await setupDriver(); + editorPage = new EditorPage( driver ); + } ); + + it( 'should be able to see visual editor', async () => { + await expect( editorPage.getBlockList() ).resolves.toBe( true ); + } ); + + it( 'should be able to add an image block', async () => { + await editorPage.addNewImageBlock(); + let imageBlock = await editorPage.getImageBlockAtPosition( 1 ); + + // Can only add image from media library on iOS + if ( ! isAndroid() ) { + await editorPage.selectEmptyImageBlock( imageBlock ); + await editorPage.chooseMediaLibrary(); + + // Workaround because of #952 + const titleElement = await editorPage.getTitleElement(); + await clickMiddleOfElement( driver, titleElement ); + await editorPage.dismissKeyboard(); + // end workaround + + imageBlock = await editorPage.getImageBlockAtPosition( 1 ); + await imageBlock.click(); + await swipeUp( driver, imageBlock ); + await editorPage.enterCaptionToSelectedImageBlock( testData.imageCaption ); + await editorPage.dismissKeyboard(); + imageBlock = await editorPage.getImageBlockAtPosition( 1 ); + await imageBlock.click(); + } + await editorPage.removeImageBlockAtPosition( 1 ); + } ); + + it( 'should be able to add an image block with multiple paragraph blocks', async () => { + await editorPage.addNewImageBlock(); + let imageBlock = await editorPage.getImageBlockAtPosition( 1 ); + + // Can only add image from media library on iOS + if ( ! isAndroid() ) { + await editorPage.selectEmptyImageBlock( imageBlock ); + await editorPage.chooseMediaLibrary(); + + imageBlock = await editorPage.getImageBlockAtPosition( 1 ); + await imageBlock.click(); + await swipeUp( driver, imageBlock ); + await editorPage.enterCaptionToSelectedImageBlock( testData.imageCaption ); + await editorPage.dismissKeyboard(); + } + + await editorPage.addNewParagraphBlock(); + const paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 2 ); + if ( isAndroid() ) { + await paragraphBlockElement.click(); + } + await editorPage.sendTextToParagraphBlockAtPosition( 2, testData.longText ); + + // skip HTML check for Android since we couldn't add image from media library + if ( ! isAndroid() ) { + await editorPage.verifyHtmlContent( testData.imageCompletehtml ); + } + } ); + + afterAll( async () => { + if ( ! isLocalEnvironment() ) { + driver.sauceJobStatus( allPassed ); + } + await stopDriver( driver ); + } ); +} ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-lists-end.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-lists-end.test.js new file mode 100644 index 0000000000000..ad6cc22372909 --- /dev/null +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-lists-end.test.js @@ -0,0 +1,71 @@ +/** + * @format + * */ + +/** + * Internal dependencies + */ +import EditorPage from './pages/editor-page'; +import { + setupDriver, + isLocalEnvironment, + stopDriver, + isAndroid, +} from './helpers/utils'; +import testData from './helpers/test-data'; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000000; + +describe( 'Gutenberg Editor tests for List block (end)', () => { + let driver; + let editorPage; + let allPassed = true; + + // Use reporter for setting status for saucelabs Job + if ( ! isLocalEnvironment() ) { + const reporter = { + specDone: async ( result ) => { + allPassed = allPassed && result.status !== 'failed'; + }, + }; + + jasmine.getEnv().addReporter( reporter ); + } + + beforeAll( async () => { + driver = await setupDriver(); + editorPage = new EditorPage( driver ); + } ); + + it( 'should be able to see visual editor', async () => { + await expect( editorPage.getBlockList() ).resolves.toBe( true ); + } ); + + it( 'should be able to end a List block', async () => { + await editorPage.addNewListBlock(); + const listBlockElement = await editorPage.getListBlockAtPosition( 1 ); + + // Click List block on Android to force EditText focus + if ( isAndroid() ) { + await listBlockElement.click(); + } + + // Send the first list item text + await editorPage.sendTextToListBlock( listBlockElement, testData.listItem1 ); + + // send an Enter + await editorPage.sendTextToListBlock( listBlockElement, '\n' ); + + // send an Enter + await editorPage.sendTextToListBlock( listBlockElement, '\n' ); + + await editorPage.verifyHtmlContent( testData.listEndedHtml ); + } ); + + afterAll( async () => { + if ( ! isLocalEnvironment() ) { + driver.sauceJobStatus( allPassed ); + } + await stopDriver( driver ); + } ); +} ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-lists.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-lists.test.js new file mode 100644 index 0000000000000..ad708491e1f53 --- /dev/null +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-lists.test.js @@ -0,0 +1,72 @@ +/** + * @format + * */ + +/** + * Internal dependencies + */ +import EditorPage from './pages/editor-page'; +import { + setupDriver, + isLocalEnvironment, + stopDriver, + isAndroid, +} from './helpers/utils'; +import testData from './helpers/test-data'; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000000; + +describe( 'Gutenberg Editor tests for List block', () => { + let driver; + let editorPage; + let allPassed = true; + + // Use reporter for setting status for saucelabs Job + if ( ! isLocalEnvironment() ) { + const reporter = { + specDone: async ( result ) => { + allPassed = allPassed && result.status !== 'failed'; + }, + }; + + jasmine.getEnv().addReporter( reporter ); + } + + beforeAll( async () => { + driver = await setupDriver(); + editorPage = new EditorPage( driver ); + } ); + + it( 'should be able to see visual editor', async () => { + await expect( editorPage.getBlockList() ).resolves.toBe( true ); + } ); + + it( 'should be able to add a new List block', async () => { + await editorPage.addNewListBlock(); + const listBlockElement = await editorPage.getListBlockAtPosition( 1 ); + + // Click List block on Android to force EditText focus + if ( isAndroid() ) { + await listBlockElement.click(); + } + + // Send the first list item text + await editorPage.sendTextToListBlock( listBlockElement, testData.listItem1 ); + + // send an Enter + await editorPage.sendTextToListBlock( listBlockElement, '\n' ); + + // Send the second list item text + await editorPage.sendTextToListBlock( listBlockElement, testData.listItem2 ); + + // switch to html and verify html + await editorPage.verifyHtmlContent( testData.listHtml ); + } ); + + afterAll( async () => { + if ( ! isLocalEnvironment() ) { + driver.sauceJobStatus( allPassed ); + } + await stopDriver( driver ); + } ); +} ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-paragraph.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-paragraph.test.js new file mode 100644 index 0000000000000..770b0368d3dd0 --- /dev/null +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-paragraph.test.js @@ -0,0 +1,128 @@ +/** + * @format + * */ + +/** + * Internal dependencies + */ +import EditorPage from './pages/editor-page'; +import { + setupDriver, + isLocalEnvironment, + clickMiddleOfElement, + clickBeginningOfElement, + stopDriver, + isAndroid, +} from './helpers/utils'; +import testData from './helpers/test-data'; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000000; + +describe( 'Gutenberg Editor tests for Paragraph Block', () => { + let driver; + let editorPage; + let allPassed = true; + + // Use reporter for setting status for saucelabs Job + if ( ! isLocalEnvironment() ) { + const reporter = { + specDone: async ( result ) => { + allPassed = allPassed && result.status !== 'failed'; + }, + }; + + jasmine.getEnv().addReporter( reporter ); + } + + beforeAll( async () => { + driver = await setupDriver(); + editorPage = new EditorPage( driver ); + } ); + + it( 'should be able to see visual editor', async () => { + await expect( editorPage.getBlockList() ).resolves.toBe( true ); + } ); + + it( 'should be able to add a new Paragraph block', async () => { + await editorPage.addNewParagraphBlock(); + const paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 1 ); + if ( isAndroid() ) { + await paragraphBlockElement.click(); + } + await editorPage.sendTextToParagraphBlock( paragraphBlockElement, testData.shortText ); + await editorPage.removeParagraphBlockAtPosition( 1 ); + } ); + + it( 'should be able to split one paragraph block into two', async () => { + await editorPage.addNewParagraphBlock(); + const paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 1 ); + if ( isAndroid() ) { + await paragraphBlockElement.click(); + } + await editorPage.sendTextToParagraphBlock( paragraphBlockElement, testData.shortText ); + const textViewElement = await editorPage.getTextViewForParagraphBlock( paragraphBlockElement ); + await clickMiddleOfElement( driver, textViewElement ); + await editorPage.sendTextToParagraphBlock( paragraphBlockElement, '\n' ); + expect( await editorPage.hasParagraphBlockAtPosition( 1 ) && await editorPage.hasParagraphBlockAtPosition( 2 ) ) + .toBe( true ); + + const text0 = await editorPage.getTextForParagraphBlockAtPosition( 1 ); + const text1 = await editorPage.getTextForParagraphBlockAtPosition( 2 ); + expect( text0 ).not.toBe( '' ); + expect( text1 ).not.toBe( '' ); + expect( testData.shortText ).toMatch( new RegExp( `${ text0 + text1 }|${ text0 } ${ text1 }` ) ); + + await editorPage.removeParagraphBlockAtPosition( 2 ); + await editorPage.removeParagraphBlockAtPosition( 1 ); + } ); + + it( 'should be able to merge 2 paragraph blocks into 1', async () => { + await editorPage.addNewParagraphBlock(); + let paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 1 ); + if ( isAndroid() ) { + await paragraphBlockElement.click(); + } + await editorPage.sendTextToParagraphBlock( paragraphBlockElement, testData.shortText ); + let textViewElement = await editorPage.getTextViewForParagraphBlock( paragraphBlockElement ); + await clickMiddleOfElement( driver, textViewElement ); + await editorPage.sendTextToParagraphBlock( paragraphBlockElement, '\n' ); + expect( await editorPage.hasParagraphBlockAtPosition( 1 ) && await editorPage.hasParagraphBlockAtPosition( 2 ) ) + .toBe( true ); + + const text0 = await editorPage.getTextForParagraphBlockAtPosition( 1 ); + const text1 = await editorPage.getTextForParagraphBlockAtPosition( 2 ); + paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 2 ); + if ( isAndroid() ) { + await paragraphBlockElement.click(); + } + textViewElement = await editorPage.getTextViewForParagraphBlock( paragraphBlockElement ); + await clickBeginningOfElement( driver, textViewElement ); + await editorPage.sendTextToParagraphBlock( paragraphBlockElement, '\u0008' ); + + const text = await editorPage.getTextForParagraphBlockAtPosition( 1 ); + expect( text0 + text1 ).toMatch( text ); + + expect( await editorPage.hasParagraphBlockAtPosition( 2 ) ).toBe( false ); + await editorPage.removeParagraphBlockAtPosition( 1 ); + } ); + + it( 'should be able to create a post with multiple paragraph blocks', async () => { + await editorPage.addNewParagraphBlock(); + const paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 1 ); + if ( isAndroid() ) { + await paragraphBlockElement.click(); + } + await editorPage.sendTextToParagraphBlockAtPosition( 1, testData.longText ); + + for ( let i = 3; i > 0; i-- ) { + await editorPage.removeParagraphBlockAtPosition( i ); + } + } ); + + afterAll( async () => { + if ( ! isLocalEnvironment() ) { + driver.sauceJobStatus( allPassed ); + } + await stopDriver( driver ); + } ); +} ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-paste.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-paste.test.js new file mode 100644 index 0000000000000..4b5926bd6419a --- /dev/null +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-paste.test.js @@ -0,0 +1,126 @@ +/** + * @format + * */ + +/** + * Internal dependencies + */ +import EditorPage from './pages/editor-page'; +import { + setupDriver, + isLocalEnvironment, + longPressMiddleOfElement, + tapSelectAllAboveElement, + tapCopyAboveElement, + tapPasteAboveElement, + stopDriver, + isAndroid, +} from './helpers/utils'; +import testData from './helpers/test-data'; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000000; + +describe( 'Gutenberg Editor paste tests', () => { + // skip iOS for now + if ( ! isAndroid() ) { + it( 'skips the tests on any platform other than Android', async () => { + } ); + return; + } + + let driver; + let editorPage; + let allPassed = true; + + // Use reporter for setting status for saucelabs Job + if ( ! isLocalEnvironment() ) { + const reporter = { + specDone: async ( result ) => { + allPassed = allPassed && result.status !== 'failed'; + }, + }; + + jasmine.getEnv().addReporter( reporter ); + } + + beforeAll( async () => { + driver = await setupDriver(); + await driver.setClipboard( '', 'plaintext' ); + editorPage = new EditorPage( driver ); + } ); + + it( 'copies plain text from one paragraph block and pastes in another', async () => { + await editorPage.addNewParagraphBlock(); + const paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 1 ); + + if ( isAndroid() ) { + await paragraphBlockElement.click(); + } + + await editorPage.sendTextToParagraphBlock( paragraphBlockElement, testData.pastePlainText ); + const textViewElement = await editorPage.getTextViewForParagraphBlock( paragraphBlockElement ); + + // copy content to clipboard + await longPressMiddleOfElement( driver, textViewElement ); + await tapSelectAllAboveElement( driver, textViewElement ); + await tapCopyAboveElement( driver, textViewElement ); + + // create another paragraph block + await editorPage.addNewParagraphBlock(); + const paragraphBlockElement2 = await editorPage.getParagraphBlockAtPosition( 2 ); + + if ( isAndroid() ) { + await paragraphBlockElement2.click(); + } + + const textViewElement2 = await editorPage.getTextViewForParagraphBlock( paragraphBlockElement2 ); + + // paste into second paragraph block + await longPressMiddleOfElement( driver, textViewElement2 ); + await tapPasteAboveElement( driver, textViewElement2 ); + + const text = await editorPage.getTextForParagraphBlockAtPosition( 2 ); + expect( text ).toBe( testData.pastePlainText ); + } ); + + it( 'copies styled text from one paragraph block and pastes in another', async () => { + // create paragraph block with styled text by editing html + await editorPage.setHtmlContentAndroid( testData.pasteHtmlText ); + const paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 1 ); + + if ( isAndroid() ) { + await paragraphBlockElement.click(); + } + + const textViewElement = await editorPage.getTextViewForParagraphBlock( paragraphBlockElement ); + + // copy content to clipboard + await longPressMiddleOfElement( driver, textViewElement ); + await tapSelectAllAboveElement( driver, textViewElement ); + await tapCopyAboveElement( driver, textViewElement ); + + // create another paragraph block + await editorPage.addNewParagraphBlock(); + const paragraphBlockElement2 = await editorPage.getParagraphBlockAtPosition( 2 ); + + if ( isAndroid() ) { + await paragraphBlockElement2.click(); + } + + const textViewElement2 = await editorPage.getTextViewForParagraphBlock( paragraphBlockElement2 ); + + // paste into second paragraph block + await longPressMiddleOfElement( driver, textViewElement2 ); + await tapPasteAboveElement( driver, textViewElement2 ); + + // check styled text by verifying html contents + await editorPage.verifyHtmlContent( testData.pasteHtmlTextResult ); + } ); + + afterAll( async () => { + if ( ! isLocalEnvironment() ) { + driver.sauceJobStatus( allPassed ); + } + await stopDriver( driver ); + } ); +} ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-rotation.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-rotation.test.js new file mode 100644 index 0000000000000..11e1c6495d2f4 --- /dev/null +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-rotation.test.js @@ -0,0 +1,83 @@ +/** + * @format + * */ + +/** + * Internal dependencies + */ +import EditorPage from './pages/editor-page'; +import { + setupDriver, + isLocalEnvironment, + stopDriver, + isAndroid, + toggleOrientation, +} from './helpers/utils'; +import testData from './helpers/test-data'; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000000; + +describe( 'Gutenberg Editor tests', () => { + let driver; + let editorPage; + let allPassed = true; + + // Use reporter for setting status for saucelabs Job + if ( ! isLocalEnvironment() ) { + const reporter = { + specDone: async ( result ) => { + allPassed = allPassed && result.status !== 'failed'; + }, + }; + + jasmine.getEnv().addReporter( reporter ); + } + + beforeAll( async () => { + driver = await setupDriver(); + editorPage = new EditorPage( driver ); + } ); + + it( 'should be able to see visual editor', async () => { + await expect( editorPage.getBlockList() ).resolves.toBe( true ); + } ); + + it( 'should be able to add blocks , rotate device and continue adding blocks', async () => { + await editorPage.addNewParagraphBlock(); + let paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 1 ); + if ( isAndroid() ) { + await paragraphBlockElement.click(); + } + + await editorPage.sendTextToParagraphBlock( paragraphBlockElement, testData.mediumText ); + + await toggleOrientation( driver ); + // On Android the keyboard hides the add block button, let's hide it after rotation + if ( isAndroid() ) { + await driver.hideDeviceKeyboard(); + } + + await editorPage.addNewParagraphBlock(); + + if ( isAndroid() ) { + await driver.hideDeviceKeyboard(); + } + + paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 2 ); + while ( ! paragraphBlockElement ) { + await driver.hideDeviceKeyboard(); + paragraphBlockElement = await editorPage.getParagraphBlockAtPosition( 2 ); + } + await editorPage.sendTextToParagraphBlock( paragraphBlockElement, testData.mediumText ); + await toggleOrientation( driver ); + + await editorPage.verifyHtmlContent( testData.deviceRotationHtml ); + } ); + + afterAll( async () => { + if ( ! isLocalEnvironment() ) { + driver.sauceJobStatus( allPassed ); + } + await stopDriver( driver ); + } ); +} ); diff --git a/packages/react-native-editor/__device-tests__/helpers/appium-local.js b/packages/react-native-editor/__device-tests__/helpers/appium-local.js new file mode 100644 index 0000000000000..31f7f878276cf --- /dev/null +++ b/packages/react-native-editor/__device-tests__/helpers/appium-local.js @@ -0,0 +1,49 @@ +/** + * @flow + * @format + * */ + +/** + * External dependencies + */ +import childProcess from 'child_process'; + +// Spawns an appium process +export const start = ( localAppiumPort: number ) => new Promise < childProcess.ChildProcess > ( ( resolve, reject ) => { + const appium = childProcess.spawn( 'appium', [ + '--port', localAppiumPort.toString(), + '--log', './appium-out.log', + '--log-no-colors', + '--relaxed-security', // Needed for mobile:shell commend for text entry on Android + ] ); + + let appiumOutputBuffer = ''; + let resolved = false; + appium.stdout.on( 'data', ( data ) => { + if ( ! resolved ) { + appiumOutputBuffer += data.toString(); + if ( appiumOutputBuffer.indexOf( 'Appium REST http interface listener started' ) >= 0 ) { + resolved = true; + resolve( appium ); + } + } + } ); + + appium.on( 'close', ( code ) => { + if ( ! resolved ) { + reject( new Error( `Appium process exited with code ${ code }` ) ); + } + } ); +} ); + +export const stop = async ( appium: ?childProcess.ChildProcess ) => { + if ( ! appium ) { + return; + } + await appium.kill( 'SIGINT' ); +}; + +export default { + start, + stop, +}; diff --git a/packages/react-native-editor/__device-tests__/helpers/caps.js b/packages/react-native-editor/__device-tests__/helpers/caps.js new file mode 100644 index 0000000000000..446eb50328b44 --- /dev/null +++ b/packages/react-native-editor/__device-tests__/helpers/caps.js @@ -0,0 +1,25 @@ +exports.ios = { + browserName: '', + platformName: 'iOS', + platformVersion: '13.0', + deviceName: 'iPhone 11 Simulator', + os: 'iOS', + deviceOrientation: 'portrait', + automationName: 'XCUITest', + appiumVersion: '1.15.0', // SauceLabs requires appiumVersion to be specified. + app: undefined, // will be set later, locally this is relative to root of project +}; + +exports.android8 = { + browserName: '', + platformName: 'Android', + platformVersion: '9.0', + deviceName: 'Google Pixel 3 XL GoogleAPI Emulator', + automationName: 'UiAutomator2', + os: 'Android', + appPackage: 'com.gutenberg', + appActivity: 'com.gutenberg.MainActivity', + deviceOrientation: 'portrait', + appiumVersion: '1.15.0', + app: undefined, +}; diff --git a/packages/react-native-editor/__device-tests__/helpers/serverConfigs.js b/packages/react-native-editor/__device-tests__/helpers/serverConfigs.js new file mode 100644 index 0000000000000..d675a65229edc --- /dev/null +++ b/packages/react-native-editor/__device-tests__/helpers/serverConfigs.js @@ -0,0 +1,10 @@ +exports.local = { + host: 'localhost', + port: 4723, // Port for local Appium runs +}; + +exports.sauce = { + host: 'ondemand.saucelabs.com', + port: 80, + auth: process.env.SAUCE_USERNAME + ':' + process.env.SAUCE_ACCESS_KEY, +}; diff --git a/packages/react-native-editor/__device-tests__/helpers/test-data.js b/packages/react-native-editor/__device-tests__/helpers/test-data.js new file mode 100644 index 0000000000000..817c94abdbc6b --- /dev/null +++ b/packages/react-native-editor/__device-tests__/helpers/test-data.js @@ -0,0 +1,89 @@ +exports.shortText = `Rock music approaches at high velocity.`; + +exports.mediumText = `The finer continuum interprets the polynomial rabbit. When can the geology runs? An astronomer runs. Should a communist consent?`; + +exports.longText = `Beneath the busy continuum blinks the ineffective husband. Why a metric now outside the official subway? How can the prompt crop exhaust his tree +Does this chord crowd my emptied search? A theory bubbles under the cartoon. The discontinued speaker cracks every thick epic. extraordinary twin shifts behind +The finer continuum interprets the polynomial rabbit. When can the geology runs? An astronomer runs. Should a communist consent?`; + +exports.listItem1 = `Milk`; +exports.listItem2 = `Honey`; +exports.listHtml = ` +
  • Milk
  • Honey
+`; +exports.listEndedHtml = ` +
  • Milk
+ + + +

+`; +exports.heading = 'Lorem Ipsum'; + +exports.pastePlainText = `Hello paste`; + +const pastedHtmlText = ` +

Hello paste

+`; + +exports.pasteHtmlText = pastedHtmlText; + +exports.pasteHtmlTextResult = `${ pastedHtmlText }\n\n${ pastedHtmlText }`; + +exports.deviceRotationHtml = ` +

The finer continuum interprets the polynomial rabbit. When can the geology runs? An astronomer runs. Should a communist consent?

+ + + +

The finer continuum interprets the polynomial rabbit. When can the geology runs? An astronomer runs. Should a communist consent?

+`; + +exports.blockInsertionHtml = ` +

Beneath the busy continuum blinks the ineffective husband. Why a metric now outside the official subway? How can the prompt crop exhaust his tree

+ + + +

Does this chord crowd my emptied search? A theory bubbles under the cartoon. The discontinued speaker cracks every thick epic. extraordinary twin shifts behind

+ + + +

The finer continuum interprets the polynomial rabbit. When can the geology runs? An astronomer runs. Should a communist consent?

+ + + +

The finer continuum interprets the polynomial rabbit. When can the geology runs? An astronomer runs. Should a communist consent?

+`; + +exports.blockInsertionHtmlFromTitle = ` +

The finer continuum interprets the polynomial rabbit. When can the geology runs? An astronomer runs. Should a communist consent?

+ + + +

Beneath the busy continuum blinks the ineffective husband. Why a metric now outside the official subway? How can the prompt crop exhaust his tree

+ + + +

Does this chord crowd my emptied search? A theory bubbles under the cartoon. The discontinued speaker cracks every thick epic. extraordinary twin shifts behind

+ + + +

The finer continuum interprets the polynomial rabbit. When can the geology runs? An astronomer runs. Should a communist consent?

+`; + +exports.imageCaption = `C'est la vie my friends`; + +exports.imageCompletehtml = ` +
C'est la vie my friends
+ + + +

Beneath the busy continuum blinks the ineffective husband. Why a metric now outside the official subway? How can the prompt crop exhaust his tree

+ + + +

Does this chord crowd my emptied search? A theory bubbles under the cartoon. The discontinued speaker cracks every thick epic. extraordinary twin shifts behind

+ + + +

The finer continuum interprets the polynomial rabbit. When can the geology runs? An astronomer runs. Should a communist consent?

+`; diff --git a/packages/react-native-editor/__device-tests__/helpers/utils.js b/packages/react-native-editor/__device-tests__/helpers/utils.js new file mode 100644 index 0000000000000..525c726d99ead --- /dev/null +++ b/packages/react-native-editor/__device-tests__/helpers/utils.js @@ -0,0 +1,352 @@ +/** + * @flow + * @format + * */ + +/** + * External dependencies + */ +import childProcess from 'child_process'; +import wd from 'wd'; +import crypto from 'crypto'; +import path from 'path'; + +/** + * Internal dependencies + */ +import serverConfigs from './serverConfigs'; +import { ios, android8 } from './caps'; +import AppiumLocal from './appium-local'; +import _ from 'underscore'; + +// Platform setup +const defaultPlatform = 'android'; +const rnPlatform = process.env.TEST_RN_PLATFORM || defaultPlatform; + +// Environment setup, local environment or Sauce Labs +const defaultEnvironment = 'local'; +const testEnvironment = process.env.TEST_ENV || defaultEnvironment; + +// Local App Paths +const defaultAndroidAppPath = './android/app/build/outputs/apk/debug/app-debug.apk'; +const defaultIOSAppPath = './ios/build/gutenberg/Build/Products/Release-iphonesimulator/gutenberg.app'; + +const localAndroidAppPath = process.env.ANDROID_APP_PATH || defaultAndroidAppPath; +const localIOSAppPath = process.env.IOS_APP_PATH || defaultIOSAppPath; + +const localAppiumPort = serverConfigs.local.port; // Port to spawn appium process for local runs +let appiumProcess: ?childProcess.ChildProcess; + +// Used to map unicode and special values to keycodes on Android +// Docs for keycode values: https://developer.android.com/reference/android/view/KeyEvent.html +const strToKeycode = { + '\n': 66, + '\u0008': 67, +}; + +const timer = ( ms: number ) => new Promise < {} > ( ( res ) => setTimeout( res, ms ) ); + +const isAndroid = () => { + return rnPlatform.toLowerCase() === 'android'; +}; + +const isLocalEnvironment = () => { + return testEnvironment.toLowerCase() === 'local'; +}; + +// Initialises the driver and desired capabilities for appium +const setupDriver = async () => { + const branch = process.env.CIRCLE_BRANCH || ''; + const safeBranchName = branch.replace( /\//g, '-' ); + if ( isLocalEnvironment() ) { + try { + appiumProcess = await AppiumLocal.start( localAppiumPort ); + } catch ( err ) { + // Ignore error here, Appium is probably already running (Appium desktop has its own server for instance) + // eslint-disable-next-line no-console + await console.log( 'Could not start Appium server', err.toString() ); + } + } + + const serverConfig = isLocalEnvironment() ? serverConfigs.local : serverConfigs.sauce; + const driver = wd.promiseChainRemote( serverConfig ); + + let desiredCaps; + if ( isAndroid() ) { + desiredCaps = _.clone( android8 ); + if ( isLocalEnvironment() ) { + desiredCaps.app = path.resolve( localAndroidAppPath ); + try { + const androidVersion = childProcess + .execSync( 'adb shell getprop ro.build.version.release' ) + .toString() + .replace( /^\s+|\s+$/g, '' ); + delete desiredCaps.platformVersion; + desiredCaps.deviceName = 'Android Emulator'; + // eslint-disable-next-line no-console + console.log( 'Detected Android device running Android %s', androidVersion ); + } catch ( error ) { + // ignore error + } + } else { + desiredCaps.app = `sauce-storage:Gutenberg-${ safeBranchName }.apk`; // App should be preloaded to sauce storage, this can also be a URL + } + } else { + desiredCaps = _.clone( ios ); + if ( isLocalEnvironment() ) { + desiredCaps.app = path.resolve( localIOSAppPath ); + delete desiredCaps.platformVersion; + desiredCaps.deviceName = 'iPhone 11'; + } else { + desiredCaps.app = `sauce-storage:Gutenberg-${ safeBranchName }.app.zip`; // App should be preloaded to sauce storage, this can also be a URL + } + } + + if ( ! isLocalEnvironment() ) { + desiredCaps.name = `Gutenberg Editor Tests[${ rnPlatform }]-${ branch }`; + desiredCaps.tags = [ 'Gutenberg', branch ]; + } + + await driver.init( desiredCaps ); + + const status = await driver.status(); + // Display the driver status + // eslint-disable-next-line no-console + console.log( status ); + + await driver.setImplicitWaitTimeout( 2000 ); + await timer( 3000 ); + await driver.setOrientation( 'PORTRAIT' ); + + // Proxy driver to patch functions on Android + // This is needed to adapt to changes in the way accessibility ids are being + // assigned after migrating to AndroidX and React Native 0.60. See: + // https://github.com/wordpress-mobile/gutenberg-mobile/pull/1112#issuecomment-501165250 + // for more details. + return new Proxy( driver, { + get: ( original, property ) => { + const propertiesToPatch = [ + 'elementByAccessibilityId', + 'hasElementByAccessibilityId', + ]; + if ( isAndroid() && ( propertiesToPatch.includes( property ) ) ) { + return async function( value, cb ) { + // Add a comma and a space to all ids + return await original[ property ]( `${ value }, `, cb ); + }; + } + return original[ property ]; + }, + } ); +}; + +const stopDriver = async ( driver: wd.PromiseChainWebdriver ) => { + if ( ! isLocalEnvironment() ) { + const jobID = driver.sessionID; + + const hash = crypto.createHmac( 'md5', jobID ) + .update( serverConfigs.sauce.auth ) + .digest( 'hex' ); + const jobURL = `https://saucelabs.com/jobs/${ jobID }?auth=${ hash }.`; + // eslint-disable-next-line no-console + console.log( `You can view the video of this test run at ${ jobURL }` ); + } + if ( driver === undefined ) { + return; + } + await driver.quit(); + + if ( appiumProcess !== undefined ) { + await AppiumLocal.stop( appiumProcess ); + } +}; + +const typeString = async ( driver: wd.PromiseChainWebdriver, element: wd.PromiseChainWebdriver.Element, str: string, clear: boolean = false ) => { + if ( clear ) { + await element.clear(); + } + + if ( isAndroid() ) { + const paragraphs = str.split( '\n' ); + + for ( let i = 0; i < paragraphs.length; i++ ) { + const paragraph = paragraphs[ i ].replace( /[ ]/g, '%s' ); + if ( paragraph in strToKeycode ) { + await driver.pressKeycode( strToKeycode[ paragraph ] ); + } else { + // Execute with adb shell input since normal type auto clears field on Android + await driver.execute( 'mobile: shell', { command: 'input', args: [ 'text', paragraph ] } ); + } + if ( i !== paragraphs.length - 1 ) { + await driver.pressKeycode( strToKeycode[ '\n' ] ); + } + } + } else { + return await element.type( str ); + } +}; + +// Calculates middle x,y and clicks that position +const clickMiddleOfElement = async ( driver: wd.PromiseChainWebdriver, element: wd.PromiseChainWebdriver.Element ) => { + const location = await element.getLocation(); + const size = await element.getSize(); + + const action = await new wd.TouchAction( driver ); + action.press( { x: location.x + ( size.width / 2 ), y: location.y } ); + action.release(); + await action.perform(); +}; + +// Clicks in the top left of an element +const clickBeginningOfElement = async ( driver: wd.PromiseChainWebdriver, element: wd.PromiseChainWebdriver.Element ) => { + const location = await element.getLocation(); + const action = await new wd.TouchAction( driver ); + action.press( { x: location.x, y: location.y } ); + action.release(); + await action.perform(); +}; + +// long press to activate context menu +const longPressMiddleOfElement = async ( driver: wd.PromiseChainWebdriver, element: wd.PromiseChainWebdriver.Element ) => { + const location = await element.getLocation(); + const size = await element.getSize(); + + const action = await new wd.TouchAction( driver ); + const x = location.x + ( size.width / 2 ); + const y = location.y + ( size.height / 2 ); + action.press( { x, y } ); + action.wait( 2000 ); + action.release(); + await action.perform(); +}; + +// press "Select All" in floating context menu +const tapSelectAllAboveElement = async ( driver: wd.PromiseChainWebdriver, element: wd.PromiseChainWebdriver.Element ) => { + const location = await element.getLocation(); + const action = await new wd.TouchAction( driver ); + const x = location.x + 300; + const y = location.y - 50; + action.press( { x, y } ); + action.release(); + await action.perform(); +}; + +// press "Copy" in floating context menu +const tapCopyAboveElement = async ( driver: wd.PromiseChainWebdriver, element: wd.PromiseChainWebdriver.Element ) => { + const location = await element.getLocation(); + const action = await new wd.TouchAction( driver ); + const x = location.x + 220; + const y = location.y - 50; + action.wait( 2000 ); + action.press( { x, y } ); + action.wait( 2000 ); + action.release(); + await action.perform(); +}; + +// press "Paste" in floating context menu +const tapPasteAboveElement = async ( driver: wd.PromiseChainWebdriver, element: wd.PromiseChainWebdriver.Element ) => { + const location = await element.getLocation(); + const action = await new wd.TouchAction( driver ); + action.wait( 2000 ); + action.press( { x: location.x + 100, y: location.y - 50 } ); + action.wait( 2000 ); + action.release(); + await action.perform(); +}; + +// Starts from the middle of the screen or the element(if specified) +// and swipes upwards +const swipeUp = async ( driver: wd.PromiseChainWebdriver, element: wd.PromiseChainWebdriver.Element = undefined ) => { + let size = await driver.getWindowSize(); + let y = 0; + if ( element !== undefined ) { + size = await element.getSize(); + const location = await element.getLocation(); + y = location.y; + } + + const startX = size.width / 2; + const startY = y + ( size.height / 3 ); + const endX = startX; + const endY = startY + ( startY * -1 * 0.5 ); + + const action = await new wd.TouchAction( driver ); + action.press( { x: startX, y: startY } ); + action.wait( 3000 ); + action.moveTo( { x: endX, y: endY } ); + action.release(); + await action.perform(); +}; + +// Starts from the middle of the screen and swipes downwards +const swipeDown = async ( driver: wd.PromiseChainWebdriver ) => { + const size = await driver.getWindowSize(); + const y = 0; + + const startX = size.width / 2; + const startY = y + ( size.height / 3 ); + const endX = startX; + const endY = startY - ( startY * -1 * 0.5 ); + + const action = await new wd.TouchAction( driver ); + action.press( { x: startX, y: startY } ); + action.wait( 3000 ); + action.moveTo( { x: endX, y: endY } ); + action.release(); + await action.perform(); +}; + +const toggleHtmlMode = async ( driver: wd.PromiseChainWebdriver, toggleOn: boolean ) => { + if ( isAndroid() ) { + // Hit the "Menu" key + await driver.pressKeycode( 82 ); + + // Go at the end of the popup to hit the "Show html" + // TODO: c'mon, find a more robust way to hit that item! :( + for ( let i = 0; i < 10; i++ ) { + await driver.pressKeycode( 20 ); + } + + // hit Enter + await driver.pressKeycode( 66 ); + } else { + const menuButton = await driver.elementByAccessibilityId( '...' ); + await menuButton.click(); + let toggleHtmlButton; + if ( toggleOn ) { + toggleHtmlButton = await driver.elementByAccessibilityId( 'Switch to HTML' ); + } else { + toggleHtmlButton = await driver.elementByAccessibilityId( 'Switch To Visual' ); + } + await toggleHtmlButton.click(); + } +}; + +const toggleOrientation = async ( driver: wd.PromiseChainWebdriver ) => { + const orientation = await driver.getOrientation(); + if ( orientation === 'LANDSCAPE' ) { + await driver.setOrientation( 'PORTRAIT' ); + } else { + await driver.setOrientation( 'LANDSCAPE' ); + } +}; + +module.exports = { + timer, + setupDriver, + isLocalEnvironment, + isAndroid, + typeString, + clickMiddleOfElement, + clickBeginningOfElement, + longPressMiddleOfElement, + tapSelectAllAboveElement, + tapCopyAboveElement, + tapPasteAboveElement, + swipeDown, + swipeUp, + stopDriver, + toggleHtmlMode, + toggleOrientation, +}; diff --git a/packages/react-native-editor/__device-tests__/pages/editor-page.js b/packages/react-native-editor/__device-tests__/pages/editor-page.js new file mode 100644 index 0000000000000..bbb4a5b74a55f --- /dev/null +++ b/packages/react-native-editor/__device-tests__/pages/editor-page.js @@ -0,0 +1,417 @@ +/** + * @flow + * @format + * */ +/** + * External dependencies + */ +import wd from 'wd'; + +/** + * Internal dependencies + */ +import { isAndroid, swipeUp, swipeDown, typeString, toggleHtmlMode } from '../helpers/utils'; + +export default class EditorPage { + driver: wd.PromiseChainWebdriver; + accessibilityIdKey: string; + accessibilityIdXPathAttrib: string; + paragraphBlockName = 'Paragraph'; + listBlockName = 'List'; + headingBlockName = 'Heading'; + imageBlockName = 'Image'; + + // This is needed to adapt to changes in the way accessibility ids are being + // assigned after migrating to AndroidX and React Native 0.60. See: + // https://github.com/wordpress-mobile/gutenberg-mobile/pull/1112#issuecomment-501165250 + // for more details. + accessibilityIdSuffix = ''; + + constructor( driver: wd.PromiseChainWebdriver ) { + this.driver = driver; + this.accessibilityIdKey = 'name'; + this.accessibilityIdXPathAttrib = 'name'; + + if ( isAndroid() ) { + this.accessibilityIdXPathAttrib = 'content-desc'; + this.accessibilityIdKey = 'contentDescription'; + this.accessibilityIdSuffix = ', '; + } + } + + async getBlockList() { + return await this.driver.hasElementByAccessibilityId( 'block-list' ); + } + + // Finds the wd element for new block that was added and sets the element attribute + // and accessibilityId attributes on this object and selects the block + // position uses one based numbering + async getBlockAtPosition( position: number, blockName: string, options: { autoscroll: boolean } = { autoscroll: false } ) { + const blockLocator = `//*[contains(@${ this.accessibilityIdXPathAttrib }, "${ blockName } Block. Row ${ position }.")]`; + const elements = await this.driver.elementsByXPath( blockLocator ); + const lastElementFound = elements[ elements.length - 1 ]; + if ( elements.length === 0 && options.autoscroll ) { + const firstBlockVisible = await this.getFirstBlockVisible(); + const lastBlockVisible = await this.getLastBlockVisible(); + // exit if no block is found + if ( ! firstBlockVisible || ! lastBlockVisible ) { + return lastElementFound; + } + const firstBlockAccessibilityId = await firstBlockVisible.getAttribute( this.accessibilityIdKey ); + const firstBlockRowMatch = /Row (\d+)\./.exec( firstBlockAccessibilityId ); + const firstBlockRow = firstBlockRowMatch && Number( firstBlockRowMatch[ 1 ] ); + const lastBlockAccessibilityId = await lastBlockVisible.getAttribute( this.accessibilityIdKey ); + const lastBlockRowMatch = /Row (\d+)\./.exec( lastBlockAccessibilityId ); + const lastBlockRow = lastBlockRowMatch && Number( lastBlockRowMatch[ 1 ] ); + if ( firstBlockRow && position < firstBlockRow ) { + if ( firstBlockRow === 1 ) { // we're at the top already stop recursing + return lastElementFound; + } + // scroll up + await swipeDown( this.driver ); + } else if ( lastBlockRow && position > lastBlockRow ) { + // scroll down + await swipeUp( this.driver ); + } + return this.getBlockAtPosition( position, blockName, options ); + } + return lastElementFound; + } + + async getFirstBlockVisible() { + const firstBlockLocator = `//*[contains(@${ this.accessibilityIdXPathAttrib }, " Block. Row ")]`; + const elements = await this.driver.elementsByXPath( firstBlockLocator ); + return elements[ 0 ]; + } + + async getLastBlockVisible() { + const firstBlockLocator = `//*[contains(@${ this.accessibilityIdXPathAttrib }, " Block. Row ")]`; + const elements = await this.driver.elementsByXPath( firstBlockLocator ); + return elements[ elements.length - 1 ]; + } + + async hasBlockAtPosition( position: number, blockName: string = '' ) { + return undefined !== await this.getBlockAtPosition( position, blockName ); + } + + async getTitleElement( options: { autoscroll: boolean } = { autoscroll: false } ) { + //TODO: Improve the identifier for this element + const elements = await this.driver.elementsByXPath( `//*[contains(@${ this.accessibilityIdXPathAttrib }, "Post title.")]` ); + if ( elements.length === 0 && options.autoscroll ) { + await swipeDown( this.driver ); + return this.getTitleElement( options ); + } + return elements[ elements.length - 1 ]; + } + + async getTextViewForHtmlViewContent() { + const accessibilityId = `html-view-content${ this.accessibilityIdSuffix }`; + let blockLocator = `//*[@${ this.accessibilityIdXPathAttrib }="${ accessibilityId }"]`; + + if ( ! isAndroid() ) { + blockLocator += '//XCUIElementTypeTextView'; + } + return await this.driver.elementByXPath( blockLocator ); + } + + // Converts to lower case and checks for a match to lowercased html content + // Ensure to take additional steps to handle text being changed by auto correct + async verifyHtmlContent( html: string ) { + await toggleHtmlMode( this.driver, true ); + + const htmlContentView = await this.getTextViewForHtmlViewContent(); + const text = await htmlContentView.text(); + expect( text.toLowerCase() ).toBe( html.toLowerCase() ); + + await toggleHtmlMode( this.driver, false ); + } + + // set html editor content explicitly + async setHtmlContentAndroid( html: string ) { + await toggleHtmlMode( this.driver, true ); + + const htmlContentView = await this.getTextViewForHtmlViewContent(); + await htmlContentView.setText( html ); + + await toggleHtmlMode( this.driver, false ); + } + + async dismissKeyboard() { + await this.driver.sleep( 1000 ); /// wait for any keyboard animations + const keyboardShown = await this.driver.isKeyboardShown(); + if ( ! keyboardShown ) { + return; + } + if ( isAndroid() ) { + return await this.driver.hideDeviceKeyboard(); + } + const hideKeyboardToolbarButton = await this.driver.elementByXPath( '//XCUIElementTypeButton[@name="Hide keyboard"]' ); + await hideKeyboardToolbarButton.click(); + } + + // ========================= + // Block toolbar functions + // ========================= + + async addNewBlock( blockName: string ) { + // Click add button + let identifier = 'Add block'; + if ( isAndroid() ) { + identifier = 'Add block, Double tap to add a block'; + } + const addButton = await this.driver.elementByAccessibilityId( identifier ); + await addButton.click(); + + // Click on block of choice + const blockButton = await this.driver.elementByAccessibilityId( blockName ); + await blockButton.click(); + } + + // ========================= + // Inline toolbar functions + // ========================= + + // position of the block to move up + async moveBlockUpAtPosition( position: number, blockName: string = '' ) { + if ( ! await this.hasBlockAtPosition( position, blockName ) ) { + throw Error( `No Block at position ${ position }` ); + } + const parentId = `${ blockName } Block. Row ${ position }.${ this.accessibilityIdSuffix }`; + const parentLocator = `//*[@${ this.accessibilityIdXPathAttrib }="${ parentId }"]`; + + const blockId = `Move block up from row ${ position } to row ${ position - 1 }${ this.accessibilityIdSuffix }`; + let blockLocator = `${ parentLocator }/following-sibling::*`; + blockLocator += isAndroid() ? '' : '//*'; + blockLocator += `[@${ this.accessibilityIdXPathAttrib }="${ blockId }"]`; + const moveUpButton = await this.driver.elementByXPath( blockLocator ); + await moveUpButton.click(); + } + + // position of the block to move down + async moveBlockDownAtPosition( position: number, blockName: string = '' ) { + if ( ! await this.hasBlockAtPosition( position, blockName ) ) { + throw Error( `No Block at position ${ position }` ); + } + + const parentId = `${ blockName } Block. Row ${ position }.`; + const parentLocator = `//*[contains(@${ this.accessibilityIdXPathAttrib }, "${ parentId }")]`; + + const blockId = `Move block down from row ${ position } to row ${ position + 1 }${ this.accessibilityIdSuffix }`; + let blockLocator = `${ parentLocator }/following-sibling::*`; + blockLocator += isAndroid() ? '' : '//*'; + blockLocator += `[@${ this.accessibilityIdXPathAttrib }="${ blockId }"]`; + const moveDownButton = await this.driver.elementByXPath( blockLocator ); + await moveDownButton.click(); + } + + // position of the block to remove + // Block will no longer be present if this succeeds + async removeBlockAtPosition( position: number, blockName: string = '' ) { + if ( ! await this.hasBlockAtPosition( position, blockName ) ) { + throw Error( `No Block at position ${ position }` ); + } + + const parentId = `${ blockName } Block. Row ${ position }.`; + const parentLocator = `//*[contains(@${ this.accessibilityIdXPathAttrib }, "${ parentId }")]`; + let removeBlockLocator = `${ parentLocator }`; + removeBlockLocator += isAndroid() ? '//*' : '//XCUIElementTypeButton'; + let removeButtonIdentifier = `Remove block at row ${ position }`; + + if ( isAndroid() ) { + removeButtonIdentifier += `, Double tap to remove the block${ this.accessibilityIdSuffix }`; + const block = await this.getBlockAtPosition( position, blockName ); + let checkList = await this.driver.elementsByXPath( removeBlockLocator ); + while ( checkList.length === 0 ) { + await swipeUp( this.driver, block ); // Swipe up to show remove icon at the bottom + checkList = await this.driver.elementsByXPath( removeBlockLocator ); + } + } + + removeBlockLocator += `[@${ this.accessibilityIdXPathAttrib }="${ removeButtonIdentifier }"]`; + const removeButton = await this.driver.elementByXPath( removeBlockLocator ); + await removeButton.click(); + } + + // ========================= + // Paragraph Block functions + // ========================= + + async addNewParagraphBlock() { + await this.addNewBlock( this.paragraphBlockName ); + } + + async getParagraphBlockAtPosition( position: number, options: { autoscroll: boolean } = { autoscroll: false } ) { + return this.getBlockAtPosition( position, this.paragraphBlockName, options ); + } + + async hasParagraphBlockAtPosition( position: number ) { + return this.hasBlockAtPosition( position, this.paragraphBlockName ); + } + + async getTextViewForParagraphBlock( block: wd.PromiseChainWebdriver.Element ) { + let textViewElementName = 'XCUIElementTypeTextView'; + if ( isAndroid() ) { + textViewElementName = 'android.widget.EditText'; + } + + const accessibilityId = await block.getAttribute( this.accessibilityIdKey ); + const blockLocator = `//*[@${ this.accessibilityIdXPathAttrib }=${ JSON.stringify( accessibilityId ) }]//${ textViewElementName }`; + return await this.driver.elementByXPath( blockLocator ); + } + + async sendTextToParagraphBlock( block: wd.PromiseChainWebdriver.Element, text: string ) { + const textViewElement = await this.getTextViewForParagraphBlock( block ); + await typeString( this.driver, textViewElement, text ); + await this.driver.sleep( 1000 ); // Give time for the block to rerender (such as for accessibility) + } + + async sendTextToParagraphBlockAtPosition( position: number, text: string ) { + const paragraphs = text.split( '\n' ); + for ( let i = 0; i < paragraphs.length; i++ ) { + // Select block first + const block = await this.getParagraphBlockAtPosition( position + i ); + await block.click(); + + await this.sendTextToParagraphBlock( block, paragraphs[ i ] ); + if ( i !== paragraphs.length - 1 ) { + await this.sendTextToParagraphBlock( block, '\n' ); + } + } + } + + async getTextForParagraphBlock( block: wd.PromiseChainWebdriver.Element ) { + const textViewElement = await this.getTextViewForParagraphBlock( block ); + const text = await textViewElement.text(); + return text.toString(); + } + + async removeParagraphBlockAtPosition( position: number ) { + await this.removeBlockAtPosition( position, this.paragraphBlockName ); + } + + async getTextForParagraphBlockAtPosition( position: number ) { + // Select block first + let block = await this.getParagraphBlockAtPosition( position ); + await block.click(); + + block = await this.getParagraphBlockAtPosition( position ); + const text = await this.getTextForParagraphBlock( block ); + return text.toString(); + } + + // ========================= + // List Block functions + // ========================= + + async addNewListBlock() { + await this.addNewBlock( this.listBlockName ); + } + + async getListBlockAtPosition( position: number ) { + return this.getBlockAtPosition( position, this.listBlockName ); + } + + async hasListBlockAtPosition( position: number ) { + return await this.hasBlockAtPosition( position, this.listBlockName ); + } + + async getTextViewForListBlock( block: wd.PromiseChainWebdriver.Element ) { + let textViewElementName = 'XCUIElementTypeTextView'; + if ( isAndroid() ) { + textViewElementName = 'android.widget.EditText'; + } + + const accessibilityId = await block.getAttribute( this.accessibilityIdKey ); + const blockLocator = `//*[@${ this.accessibilityIdXPathAttrib }=${ JSON.stringify( accessibilityId ) }]//${ textViewElementName }`; + return await this.driver.elementByXPath( blockLocator ); + } + + async sendTextToListBlock( block: wd.PromiseChainWebdriver.Element, text: string ) { + const textViewElement = await this.getTextViewForListBlock( block ); + return await typeString( this.driver, textViewElement, text ); + } + + async getTextForListBlock( block: wd.PromiseChainWebdriver.Element ) { + const textViewElement = await this.getTextViewForListBlock( block ); + const text = await textViewElement.text(); + return text.toString(); + } + + async removeListBlockAtPosition( position: number ) { + return await this.removeBlockAtPosition( position, this.listBlockName ); + } + + async getTextForListBlockAtPosition( position: number ) { + const block = await this.getListBlockAtPosition( position ); + const text = await this.getTextForListBlock( block ); + return text.toString(); + } + + // ========================= + // Image Block functions + // ========================= + + async addNewImageBlock() { + await this.addNewBlock( this.imageBlockName ); + } + + async getImageBlockAtPosition( position: number ) { + return this.getBlockAtPosition( position, this.imageBlockName ); + } + + async selectEmptyImageBlock( block: wd.PromiseChainWebdriver.Element ) { + const accessibilityId = await block.getAttribute( this.accessibilityIdKey ); + const blockLocator = `//*[@${ this.accessibilityIdXPathAttrib }="${ accessibilityId }"]//XCUIElementTypeButton[@name="Image block. Empty"]`; + const imageBlockInnerElement = await this.driver.elementByXPath( blockLocator ); + await imageBlockInnerElement.click(); + } + + async chooseMediaLibrary() { + const mediaLibraryButton = await this.driver.elementByAccessibilityId( 'WordPress Media Library' ); + await mediaLibraryButton.click(); + } + + async enterCaptionToSelectedImageBlock( caption: string ) { + const imageBlockCaptionField = await this.driver.elementByXPath( '//XCUIElementTypeButton[@name="Image caption. Empty"]' ); + await imageBlockCaptionField.click(); + await typeString( this.driver, imageBlockCaptionField, caption ); + } + + async removeImageBlockAtPosition( position: number ) { + return await this.removeBlockAtPosition( position, this.imageBlockName ); + } + + // ========================= + // Heading Block functions + // ========================= + async addNewHeadingBlock() { + await this.addNewBlock( this.headingBlockName ); + } + + async getHeadingBlockAtPosition( position: number ) { + return this.getBlockAtPosition( position, this.headingBlockName ); + } + + // Inner element changes on iOS if Heading Block is empty + async getTextViewForHeadingBlock( block: wd.PromiseChainWebdriver.Element, empty: boolean ) { + let textViewElementName = empty ? 'XCUIElementTypeStaticText' : 'XCUIElementTypeTextView'; + if ( isAndroid() ) { + textViewElementName = 'android.widget.EditText'; + } + + const accessibilityId = await block.getAttribute( this.accessibilityIdKey ); + const blockLocator = `//*[@${ this.accessibilityIdXPathAttrib }="${ accessibilityId }"]//${ textViewElementName }`; + return await this.driver.elementByXPath( blockLocator ); + } + + async sendTextToHeadingBlock( block: wd.PromiseChainWebdriver.Element, text: string ) { + const textViewElement = await this.getTextViewForHeadingBlock( block, true ); + return await typeString( this.driver, textViewElement, text ); + } + + async getTextForHeadingBlock( block: wd.PromiseChainWebdriver.Element ) { + const textViewElement = await this.getTextViewForHeadingBlock( block, false ); + const text = await textViewElement.text(); + return text.toString(); + } +} diff --git a/packages/react-native-editor/android/app/BUCK b/packages/react-native-editor/android/app/BUCK new file mode 100644 index 0000000000000..bd517c4b3f032 --- /dev/null +++ b/packages/react-native-editor/android/app/BUCK @@ -0,0 +1,55 @@ +# To learn about Buck see [Docs](https://buckbuild.com/). +# To run your application with Buck: +# - install Buck +# - `npm start` - to start the packager +# - `cd android` +# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` +# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck +# - `buck install -r android/app` - compile, install and run application +# + +load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") + +lib_deps = [] + +create_aar_targets(glob(["libs/*.aar"])) + +create_jar_targets(glob(["libs/*.jar"])) + +android_library( + name = "all-libs", + exported_deps = lib_deps, +) + +android_library( + name = "app-code", + srcs = glob([ + "src/main/java/**/*.java", + ]), + deps = [ + ":all-libs", + ":build_config", + ":res", + ], +) + +android_build_config( + name = "build_config", + package = "com.gutenberg", +) + +android_resource( + name = "res", + package = "com.gutenberg", + res = "src/main/res", +) + +android_binary( + name = "app", + keystore = "//android/keystores:debug", + manifest = "src/main/AndroidManifest.xml", + package_type = "debug", + deps = [ + ":app-code", + ], +) diff --git a/packages/react-native-editor/android/app/build.gradle b/packages/react-native-editor/android/app/build.gradle new file mode 100644 index 0000000000000..05ddeee546995 --- /dev/null +++ b/packages/react-native-editor/android/app/build.gradle @@ -0,0 +1,164 @@ +apply plugin: "com.android.application" + +import com.android.build.OutputFile + +/** + * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets + * and bundleReleaseJsAndAssets). + * These basically call `react-native bundle` with the correct arguments during the Android build + * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the + * bundle directly from the development server. Below you can see all the possible configurations + * and their defaults. If you decide to add a configuration block, make sure to add it before the + * `apply from: "../../node_modules/react-native/react.gradle"` line. + * + * project.ext.react = [ + * // the name of the generated asset file containing your JS bundle + * bundleAssetName: "index.android.bundle", + * + * // the entry file for bundle generation + * entryFile: "index.android.js", + * + * // whether to bundle JS and assets in debug mode + * bundleInDebug: false, + * + * // whether to bundle JS and assets in release mode + * bundleInRelease: true, + * + * // whether to bundle JS and assets in another build variant (if configured). + * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants + * // The configuration property can be in the following formats + * // 'bundleIn${productFlavor}${buildType}' + * // 'bundleIn${buildType}' + * // bundleInFreeDebug: true, + * // bundleInPaidRelease: true, + * // bundleInBeta: true, + * + * // whether to disable dev mode in custom build variants (by default only disabled in release) + * // for example: to disable dev mode in the staging build type (if configured) + * devDisabledInStaging: true, + * // The configuration property can be in the following formats + * // 'devDisabledIn${productFlavor}${buildType}' + * // 'devDisabledIn${buildType}' + * + * // the root of your project, i.e. where "package.json" lives + * root: "../../", + * + * // where to put the JS bundle asset in debug mode + * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", + * + * // where to put the JS bundle asset in release mode + * jsBundleDirRelease: "$buildDir/intermediates/assets/release", + * + * // where to put drawable resources / React Native assets, e.g. the ones you use via + * // require('./image.png')), in debug mode + * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", + * + * // where to put drawable resources / React Native assets, e.g. the ones you use via + * // require('./image.png')), in release mode + * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", + * + * // by default the gradle tasks are skipped if none of the JS files or assets change; this means + * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to + * // date; if you have any other folders that you want to ignore for performance reasons (gradle + * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ + * // for example, you might want to remove it from here. + * inputExcludes: ["android/**", "ios/**"], + * + * // override which node gets called and with what additional arguments + * nodeExecutableAndArgs: ["node"], + * + * // supply additional arguments to the packager + * extraPackagerArgs: [] + * ] + */ + +project.ext.react = [ + entryFile: "index.js" +] + +apply from: "../../node_modules/react-native/react.gradle" + +/** + * Set this to true to create two separate APKs instead of one: + * - An APK that only works on ARM devices + * - An APK that only works on x86 devices + * The advantage is the size of the APK is reduced by about 4MB. + * Upload all the APKs to the Play Store and people will download + * the correct one based on the CPU architecture of their device. + */ +def enableSeparateBuildPerCPUArchitecture = false + +/** + * Run Proguard to shrink the Java bytecode in release builds. + */ +def enableProguardInReleaseBuilds = false + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + applicationId "com.gutenberg" + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1 + versionName "1.0" + } + splits { + abi { + reset() + enable enableSeparateBuildPerCPUArchitecture + universalApk false // If true, also generate a universal APK + include "armeabi-v7a", "x86", "arm64-v8a", "x86-64" + } + } + buildTypes { + release { + minifyEnabled enableProguardInReleaseBuilds + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + } + } + // applicationVariants are e.g. debug, release + applicationVariants.all { variant -> + variant.outputs.each { output -> + // For each separate APK per architecture, set a unique version code as described here: + // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits + def versionCodes = ["armeabi-v7a":1, "x86":2, "arm64-v8a": 3, "x86-64": 4] + def abi = output.getFilter(OutputFile.ABI) + if (abi != null) { // null for the universal-debug, universal-release variants + output.versionCodeOverride = + versionCodes.get(abi) * 1048576 + defaultConfig.versionCode + } + } + } +} + +repositories { + google() + jcenter() + maven { url "https://jitpack.io" } +} + + +dependencies { + implementation project(':@react-native-community_slider') + implementation project(':react-native-video') + implementation project(':react-native-svg') + implementation project(':react-native-aztec') + implementation project(':react-native-recyclerview-list') + implementation project(':react-native-gutenberg-bridge') + implementation "org.wordpress:utils:$wordpressUtilsVersion" + implementation 'androidx.appcompat:appcompat:1.0.0' + implementation "com.facebook.react:react-native:+" // From node_modules +} + +// Run this once to be able to run the application with BUCK +// puts all compile dependencies into folder libs for BUCK to use +task copyDownloadableDepsToLibs(type: Copy) { + from configurations.compile + into 'libs' +} diff --git a/packages/react-native-editor/android/app/build_defs.bzl b/packages/react-native-editor/android/app/build_defs.bzl new file mode 100644 index 0000000000000..fff270f8d1d48 --- /dev/null +++ b/packages/react-native-editor/android/app/build_defs.bzl @@ -0,0 +1,19 @@ +"""Helper definitions to glob .aar and .jar targets""" + +def create_aar_targets(aarfiles): + for aarfile in aarfiles: + name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] + lib_deps.append(":" + name) + android_prebuilt_aar( + name = name, + aar = aarfile, + ) + +def create_jar_targets(jarfiles): + for jarfile in jarfiles: + name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] + lib_deps.append(":" + name) + prebuilt_jar( + name = name, + binary_jar = jarfile, + ) diff --git a/packages/react-native-editor/android/app/proguard-rules.pro b/packages/react-native-editor/android/app/proguard-rules.pro new file mode 100644 index 0000000000000..a92fa177ee49f --- /dev/null +++ b/packages/react-native-editor/android/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/packages/react-native-editor/android/app/src/debug/AndroidManifest.xml b/packages/react-native-editor/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000000000..1fce564881ff3 --- /dev/null +++ b/packages/react-native-editor/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/packages/react-native-editor/android/app/src/debug/res/xml/react_native_config.xml b/packages/react-native-editor/android/app/src/debug/res/xml/react_native_config.xml new file mode 100644 index 0000000000000..ba4b23070be6c --- /dev/null +++ b/packages/react-native-editor/android/app/src/debug/res/xml/react_native_config.xml @@ -0,0 +1,8 @@ + + + + localhost + 10.0.2.2 + 10.0.3.2 + + diff --git a/packages/react-native-editor/android/app/src/main/AndroidManifest.xml b/packages/react-native-editor/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000..3678735b5c2b5 --- /dev/null +++ b/packages/react-native-editor/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + diff --git a/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainActivity.java b/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainActivity.java new file mode 100644 index 0000000000000..f5f8ff7b247d0 --- /dev/null +++ b/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainActivity.java @@ -0,0 +1,15 @@ +package com.gutenberg; + +import com.facebook.react.ReactActivity; + +public class MainActivity extends ReactActivity { + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ + @Override + protected String getMainComponentName() { + return "gutenberg"; + } +} diff --git a/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java b/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java new file mode 100644 index 0000000000000..18b177c220186 --- /dev/null +++ b/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java @@ -0,0 +1,171 @@ +package com.gutenberg; + +import android.app.Application; +import android.util.Log; + +import androidx.core.util.Consumer; + +import com.facebook.react.ReactApplication; +import com.reactnativecommunity.slider.ReactSliderPackage; +import com.brentvatne.react.ReactVideoPackage; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.devsupport.interfaces.DevOptionHandler; +import com.facebook.react.devsupport.interfaces.DevSupportManager; +import com.horcrux.svg.SvgPackage; + +import org.wordpress.mobile.ReactNativeAztec.ReactAztecPackage; +import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent; +import org.wordpress.mobile.ReactNativeGutenbergBridge.RNReactNativeGutenbergBridgePackage; + +import com.github.godness84.RNRecyclerViewList.RNRecyclerviewListPackage; +import com.facebook.react.ReactNativeHost; +import com.facebook.react.ReactPackage; +import com.facebook.react.shell.MainReactPackage; +import com.facebook.soloader.SoLoader; + +import java.util.Arrays; +import java.util.List; + +public class MainApplication extends Application implements ReactApplication { + + private static final String TAG = "MainApplication"; + + private ReactNativeHost mReactNativeHost; + private RNReactNativeGutenbergBridgePackage mRnReactNativeGutenbergBridgePackage; + + private ReactNativeHost createReactNativeHost() { + mRnReactNativeGutenbergBridgePackage = new RNReactNativeGutenbergBridgePackage(new GutenbergBridgeJS2Parent() { + @Override + public void responseHtml(String title, String html, boolean changed) { + } + + @Override + public void requestMediaImport(String url, MediaUploadCallback mediaUploadCallback) { + } + + @Override + public void requestMediaPickerFromDeviceCamera(MediaUploadCallback mediaUploadCallback, MediaType mediaType) { + } + + @Override + public void requestMediaPickFromDeviceLibrary(MediaUploadCallback mediaUploadCallback, Boolean allowMultipleSelection, MediaType mediaType) { + } + + @Override + public void requestMediaPickFromMediaLibrary(MediaUploadCallback mediaUploadCallback, Boolean allowMultipleSelection, MediaType mediaType) { + } + + + @Override + public void mediaUploadSync(MediaUploadCallback mediaUploadCallback) { + } + + @Override + public void requestImageFailedRetryDialog(int mediaId) { + } + + @Override + public void requestImageUploadCancelDialog(int mediaId) { + } + + @Override + public void requestImageUploadCancel(int mediaId) { + } + + @Override + public void editorDidMount(ReadableArray unsupportedBlockNames) { + } + + @Override + public void editorDidAutosave() { + } + + @Override + public void getOtherMediaPickerOptions(OtherMediaOptionsReceivedCallback otherMediaOptionsReceivedCallback, MediaType mediaType) { + + } + + @Override + public void requestMediaPickFrom(String mediaSource, MediaUploadCallback mediaUploadCallback, Boolean allowMultipleSelection) { + + } + + @Override + public void requestImageFullscreenPreview(String mediaUrl) { + + } + + @Override + public void editorDidEmitLog(String message, LogLevel logLevel) { + switch (logLevel) { + case TRACE: + Log.d(TAG, message); + break; + case INFO: + Log.i(TAG, message); + break; + case WARN: + Log.w(TAG, message); + break; + case ERROR: + Log.e(TAG, message); + break; + } + } + + @Override + public void performRequest(String path, Consumer onSuccess, Consumer onError) {} + }); + + return new ReactNativeHost(this) { + @Override + public boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage(), + new ReactSliderPackage(), + new ReactVideoPackage(), + new SvgPackage(), + new ReactAztecPackage(), + new RNRecyclerviewListPackage(), + mRnReactNativeGutenbergBridgePackage); + } + + @Override + protected String getJSMainModuleName() { + return "index"; + } + }; + } + + @Override + public ReactNativeHost getReactNativeHost() { + if (mReactNativeHost == null) { + mReactNativeHost = createReactNativeHost(); + createCustomDevOptions(mReactNativeHost); + } + + return mReactNativeHost; + } + + @Override + public void onCreate() { + super.onCreate(); + SoLoader.init(this, /* native exopackage */ false); + } + + private void createCustomDevOptions(ReactNativeHost reactNativeHost) { + DevSupportManager devSupportManager = reactNativeHost.getReactInstanceManager().getDevSupportManager(); + + devSupportManager.addCustomDevOption("Show html", new DevOptionHandler() { + @Override + public void onOptionSelected() { + mRnReactNativeGutenbergBridgePackage.getRNReactNativeGutenbergBridgeModule().toggleEditorMode(); + } + }); + } +} diff --git a/packages/react-native-editor/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/react-native-editor/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..a2f5908281d070150700378b64a84c7db1f97aa1 GIT binary patch literal 3056 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 axPAxe%7rDusX+w*00006jj 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⁣ zwiG+x#%j}{0gU~J8;<|!B1@-XaB@{KORFwrYg_8rOv({b0EO#DbeQRm;B6_9=mXGf z-x|VL{zd`)#@yN}HkCSJbjbNlE|zL3Wm9Q8HY`sV)}3%pgN>cL^67{Z;PPL(*wT8N zUjXU{@|*hvm}({wsAC=x0^ok0%UAz0;sogW{B!nDqk|JJ5x~4NfTDgP49^zeu`csl?5mY@JdQdISc zFs!E{^grmkLnUk9 zny~m)1vws@5BFI<-0Tuo2JWX(0v`W|t(wg;s--L47WTvTMz-8l#TL^=OJNRS2?_Qj z3AKT+gvbyBi#H*-tJ%tWD|>EV3wy|8qxfzS!5RW;Jpl5*zo&^UBU=fG#2}UvRyNkK zA06Dy9;K1ca@r2T>yThYgI!ont$(G{6q#2QT+00r_x0(b)gsE`lBB?2gr55gq^D3Fi&p%E(p9>U%bv zkg1Jco(RbyTX7FDHOnl7-O@ zI$AaIl?9NJKPm(WiBP`1-#CB1QzU>&hKm)fpa5DKE{2$X0hGz-0uZ?cyTk(YC!Y&| zL=1VrNERSA5NA2jq7FACfX4JfPyj5XXl1yv0>~s;eF7L2$>&oMqeTFT2m$y7FlkON z_yurD1yIOvA;5C6016pyxBznGUt0kJ&k5r#;&>Jow`r)sp9R~PmK~lz$3xH%LT*1U zJdOyABZ3!FvNoR*vN$5ykHS8f`jA4zV+|L}i1C4`B2c{R0;UdYxaU|H)2avz@ z=mEYc|2S<+(B2Tj+FkX+2D+yFI!k9lWMA61DJ{)e;lum$(;O87?vGJJe!KtK04+N_ zI*P~t@dUb>9Xh{dbyl{-ZQ(UMgz7$|QfL5XSPkskt^NgctYC#;4WcZB1@%@wy@2t3 z2z0DI7&%b$*Aw~abe?GxE`ez@+6hOh-6*8fHRV{1os$EL@}uUZeG4h1&Be`98q*7j z=3-v+lhIjfWVo12!<>%V^a6lTgW3+_#W6n|p*~==zOH7z$0{LSZk(Tpd7EaD04hnA zL;#fxS0aD{`5^&D`}>0Uq?byDD-l2=!wm_bLcUl4gc(% za1p|itVANvFF>hghAS07Im1;IK;|b*W)}VDyI;BIp2=K*yu2a)j?B|f<44NI$NbmJ z#dE0>jI$fMr&@>4kN8MLFb4&2O9fEKaQg%(QO$4_1rVQywG^CmBLh#}_7gKW3vd?| z2?1^&KWq8}8I^_S0|)MowU_pw$q@nl@Nkn$z>BQq_KA^9yaR`(R3u{{Ig;cwt z@AJ^{ODQCm^neroM9nKNUAXi9RCK`OsP_LuR0PUR(YZCCX5dNF6VzcoK&=b^r`W?ltt|*F zpkoae%ZT{C1h~EcFui~b7fF`vb<<~j_VquuUA$}QqIKYELPp#;{u?q8Dz}WAG-(3; zjrm$i%7UbyZMM(Y{>!uJ#vNB?R~B{6Htp=>e*<{fQQ5W7V(1coCWlOON!MzZxhum| ztZBQpGR z;~#ur^&PockKdV{Q6R>o`Pl{0x!DEbpZ7y9Y;*ZvE!*gU`V1W3znva{f=?WO5I&>B z&hw6}tjECtaghm5z|C#%M;Yf_*pI^};h}Vl=^r9EN=tVDj86D;C$jIJ?K7VP+00000NkvXXu0mjf D5i!M* literal 0 HcmV?d00001 diff --git a/packages/react-native-editor/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/packages/react-native-editor/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..459ca609d3ae0d3943ab44cdc27feef9256dc6d7 GIT binary patch literal 7098 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 literal 0 HcmV?d00001 diff --git a/packages/react-native-editor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/react-native-editor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..8ca12fe024be86e868d14e91120a6902f8e88ac6 GIT binary patch literal 6464 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 B0*L?s literal 0 HcmV?d00001 diff --git a/packages/react-native-editor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/packages/react-native-editor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..8e19b410a1b15ff180f3dacac19395fe3046cdec GIT binary patch literal 10676 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 literal 0 HcmV?d00001 diff --git a/packages/react-native-editor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/packages/react-native-editor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..4c19a13c239cb67b8a2134ddd5f325db1d2d5bee GIT binary patch literal 15523 zcmZu&byQSev_3Py&@gnDfPjP`DLFJqiULXtibx~fLnvK>bPOP+(%nO&(%r2fA>H-( zz4z~1>*iYL?tRWZ_k8=?-?=ADTT_`3j}{LAK&YyspmTRd|F`47?v6Thw%7njTB|C^ zKKGc}$-p)u@1g1$=G5ziQhGf`pecnFHQK@{)H)R`NQF;K%92o17K-93yUfN21$b29 zQwz1oFs@r6GO|&!sP_4*_5J}y@1EmX38MLHp9O5Oe0Nc6{^^wzO4l(d z;mtZ_YZu`gPyE@_DZic*_^gGkxh<(}XliiFNpj1&`$dYO3scX$PHr^OPt}D-`w9aR z4}a$o1nmaz>bV)|i2j5($CXJ<=V0%{^_5JXJ2~-Q=5u(R41}kRaj^33P50Hg*ot1f z?w;RDqu}t{QQ%88FhO3t>0-Sy@ck7!K1c53XC+HJeY@B0BH+W}BTA1!ueRG49Clr? z+R!2Jlc`n)zZ?XWaZO0BnqvRN#k{$*;dYA4UO&o_-b>h3>@8fgSjOUsv0wVwlxy0h z{E1|}P_3K!kMbGZt_qQIF~jd+Km4P8D0dwO{+jQ1;}@_Weti;`V}a_?BkaNJA?PXD zNGH$uRwng<4o9{nk4gW z3E-`-*MB=(J%0*&SA1UclA>pLfP4H?eSsQV$G$t!uXTEio7TY9E35&?0M-ERfX4he z{_Hb&AE`T%j8hIZEp@yBVycpvW2!bHrfxbuu6>_i<^9@?ak)9gHU*#bS~}$sGY*Fi z=%P&i3aH%N`b;I~s8{&6uGo$>-`ukQ<8ri(6aH6p_F`Fhdi6HuacwfQn10HVL7Om1 z4aZpjatkbgjp$L5Mceab#G#C)Hr{^W|TJX~?B3@2buj0;kfuNTf4c3*Au~O^aj=W2$j^4okeCxh#lwexN@eam-u4dNz zN2NIuIM4566{T&^k%4ftShcPk#=im-zXm>QWqH^0>A@?MqlDZCZ@8Wi*@tvhn5p<} zRwFm@gz|WZp91S5Z{}tB^e9|FBg(~Ik+?&_53J6ye_QQOSJ*846~H%s#LD}|O9v9H z1fLrrgoPo_&bs}eqEr}2en3iqAcP^>YsKiez$5-6m6(#3ZZ$@M5Ck=_Vv`QA>1A*v z3w-nJ_;5Nc(0_%`kG91#sotIlhO!*5#|yg+Gx{V;0ty`*=Y9=jCh$l*=fE(~t}%R# zc}iNpO)OZX`P=leQY^?^DF1w%FJh>Dkp}-o5Ig|2!6^E>|W|zc~W7gF;MtxX7 zV~UjQNsUC$EYXpN?~o{83D2c*0~7;Tm~%FRTAnnt3ln{?DcLZ=NsBY|JxwUA-6K3V zP&#|9t#a}Q4{Sg{6v-OmjJBkCh>m)8vLNm4lStMUT$)FZeJG05A)px&o3H)5oAl9= z31@?HyCriHcCDnt628BFN+T;U69Wl#itfvqIDBydMvOJO0Zl?go$cfG5>TK75CMj3 zakLaH3=&J0e}Xmqlav$S0>E@_Yo_V~3SiiXrw)$&!XhrHCDQ%P1BHPusuKr0LthAB zg)mDrLy>2*yevMMOQe6fZ|)%PEb!lC^*9yaX9UMy7-v!fSICssTR|wML0Ic2BhKAq z3I1X~ z7^_!M&;6Z9?br3#HU_&kfJ~%botXQkC1v<}ZZxN5q-T)|Sb2cW3WYUBbDZ`TH{!*^ zrmAeRM+(QI>D+?}guZ+dH*X)@^!O|oL69&Avbtw2^M3HP(+2kV{O$^3BN1RLfrC8nwz7=VhBR%>!;7WR<~;34B_j3A{>^@e@H+Q! zL=UNr1(JvKAQLKT0b}EMn|QUWtY>!>8-t@fVj_&`~gGd{_aPy5W>0u5L$zrsU^rBO=i$`#Xd*>kh)lPf}A znNXSEl`+HlhXtylgS9(#N02A=zVV?#OF?)Gr>(HszVa+1*2VG@qYttJuXaBlzP`Pb zX)ueu?s&}R>xI#^*r4gR?tMFi!_eeKlIM5g)Nk)Y^h=ZCR**xY>$E5knctRrq!zw? zX{2|hwR9LXTY1)pTlKg7U4_ej{dcj2{!+1sZ6<@9^?mn)=37V)DIAvS(}S`IgFO!6 zn({?nYw`Z-@jvt@!q|5z?TI3(dx^1szSn%azAwp>N#fk^kt|=MejKtacAs@Rdku#zT>9$s z=m7ek)`=O7hO2n+2Uj$QUs&2EIqycF{(L9Y#^IyxXA%R@ z&j`VAprIV~d!pH-7~zA+bjwVn3kOB3;rlg{nr&wHV12N}g^i>Upls~=z`VX>9HQ#= zTu&luVb@_Lkz63&&^_M!6(-2^0?GCAX9XKp{O={pd|AlIMGriX6s_Jy8_q9|{5jLc zxd1aj_ucE7Vcti#$r!s~w~W=XpaLQ}#mX`apR7^n9-d3?O+adJYr*L;{c)x@REewM@vZN0njS3iE$88KHPWAkWt((OUMherUnPm?i&8@!9E@ zUW^$%CpdruZR0ohzUq-XQ$KEIB8Sjgs1+wKSUH&Y;=ee%E&O$X18{&979d~K2uJW` zd*8awHCXb;Q>4z$B|sPNv+Zd__f6&@KmS+L`z3H1x+x|Xs7-N-iw|1C=QiJdU)f~z z{vO4hpP`0MyqmwIHN=l?jSq>OKG6CEC#O`*blP`?>)CUWj5j1cB>%6N7;`kfZ1iQV zam~SDB?{uyp^=vF_u|=8xn3S)L;wF8ZRZV{bezM-EH;MC91JQZ{KcZZ$IWJUy?SJGeGUWm6PeuO8-K2|hD~p;Ls~9Y-4lE+?|bF)XaNKUNX(K7 zBQk0Z{n>hrH-CA`bTr$6z0n@Cn9EL$XZ3=X7NopjcI=;z<(X7-oEmK}BId=PxX*!b7Q6oL@ufd%eEPc`_la(}WkT zKe?-YJWn^6b$^{dhdJZ)I!Kn6c}iw%o5mLDyvM7qJZbkGG?zLU;M|W;Wis|A;SuY3{_X53`+>9g^B%O4b{;^t$^;{oKHbo*CY%u91 zp#2d8Pg=I0&UX{qwr=y=o_^BLdk=KYH$=Z8+k|p8V5`ph~3b^{^NnL4m_+4zx( zeoTt@f<$DmsB1}o%R1Hx`ToPuBl+P6cb-?uF{1!z-2WvdR4+vJ*SYTic5@gwnzu%e zD!HF^X=$ha^#1hi*@~^nDL!HQ;MC&e+6=onaJgm-J-+|>PpmU=SIe?EQE5vJiqziw z*K=Z%bWZz_we!qiFqE`I?#$yozNxIE7Ei;csv>++r*?)0bozFpF&oLh94u z-2c2L`5BarP7l>87|f)vxaT*9(!Q`2xBMZ&^JVj-|1)Tg!6OW=lk=w zLwVlr!*<(l*L$a?ox3+%!~UIj3Ej@KD;W>1E_c)1szDi93BC;0K?drOQ>@$yi|DtT zSir}!Yx>znf&b0KS;Lk7VKPDF@e>(qQr0%SNcGQd(p9StjqJ`QSW&c{ggF?5{d22w zlkX%JTUq`;(3WSH+)WHl%qlF)iNG_?}K?ZM3cS7#u5v zZ!apx4Apv=PWsn}eD%MI#=KA)OlNy0)l@~D^1;NC5k@|OPW3wt>WNYDN+8~+gM%E! z$ z`Olr0;eytiK&~O*ps%KV?2vq+DhuRh*!6Ilzu>A;iMe9 zI?zug9nT9CI_o)O}KF_I_U z_Cswu{)3pCYgw{eOt#E?UCqBwkAugSl>5 zX?G=Ci(Lo+r3suuJezyQyDvw*<1b{rx*&ZaY2HlJ>k{Qc%IZeU43pQXw4mh!4I5>l zZ@4$uxaPY#!*IhL4Hctn#!n#S+SiPcZP_PTd5fXf1exhFi5zf3kl`UcW2RUk)F2oF z_ogN`{03PiseQR;fa#{Uy;jeNlJ0Sle`~;ZYhLjkuy>a^!Z_nR~`$&F?NVuIE3HX;i zD82snwlwPb`7yE)ZA_Ndmq5zuSO1{{1}(d9u4#!Fl_|eOuxKBwOfQ*tG`VjCV$-WF zxi0c&+w}Z)rqz{%f46@`ADPdGm#x)+zpT+gyfDi;_P zR{#Ta`Mzd=putKO@5lQJO*aNy(i?}Ltwy^Z;69f|eqi#UCI1$vL!+(#mi?dK`OL$! z3jQnx$_$+Li2<__CL@Wuk4^J7-!n3j2I4N8e#=qpir+iEQcrn3`B4yNOd1BBLEni<(tdRWE>m0I^ zt(^*Td+S3}$5rOzXy=MW>%#MN_qy%5St!>HrGZ~Fq1WKw-&kv@2TrCcPCPzY%2aO- zN?7@+$4?&qA|uv{QHuV)O9haZpG7Jx2f%D)7J@oWTxJ#E_YSq_6qT1tomOD?02(1otT{Hk8{?g(944>h4f% zOJ8tzjecV{x2uWde&6oAP)*({ zFkW0Q%gdI*9@W)oKO65DgP<3F_BIKvRXLAR?Z61&0g2TR6mEZ7OZK?dP7zukdg?s_tNZeuOsh^e1Tmdlz5rIg?LcK|%aQ1FsSDv#W0EnHd z9M)p;gAL_R~Z5cojTdwy+qDsd6R01Vtxmq&FhfPz{wxmB$${zW~z@{Ro_ zK#y5^KqIp!#@or>GD`c+aZ(PV1=`Eo1?a55p6a*WepFgxvmp!^2518YEU-;{F}fLr zD~)=S0m=+px3TUN8-El}Xb}{2ET*_i3-|WlY@V7vr6#&cOr*+oS9?GF?@)K6op>>o z4af0@%KwaLr`{3P&)474<3rDMsd!IM-bepWfhfuMmJt}#0%PgDSx*q(s0m%ZFgWTj zwwvH%2!(i9{RHX~FVUB5qHvF{+ZF}+(bZVPG1)a*Ph>KV;cYNK^aB@R#dS~&`^60V zn2Z24Y{{djzK33}t@q%!v5k)u7jAXB_H{#4Ut2 z1}0j5$RXcTyfazqL9=^Qe%GL`G)=!lirv7AgVRf^=XyEM&kiOe_%JD!O?sXK&hrDo zF}m9B68im!oGshuZluy2H#T$`XPZQu@zf;(nBCZB-cjQ&w*p@Tm_$pe^MTN3EauI) zJG&G^H-4S|1OCd#@A6jO+IcAXG#5M-d9E!^YNmV7Z(=F^?8bfrYf&mLMnRd_22&Q} z2*msbLsrI!XPeOK@|V?n>`kNC`8eSFmekELLr|!-wQRltxZnuRedup<7VflowJ+gC z)F}P6lUSsh^B41?=~0*68YA6z63lKG`W$@{GV!cC2FCl0s<7yz6!3JWoBbUDTgpg% z4VNUk%xblMy7PjLF2We*3XY7K*N(*9Yx!_M zjU$&JXLiNxaTzoa&k@NSbzbLJTn$6bu6SPWYx)Zc1Li~Lqj($GuWsA#;zg85eH{yx zz3IIOea3A4QFGmJCfn7N_d$8a77j+T^W}Sr%0XdVLFf&zJ$s^D5Vrc!iV&GXyb5*A z6mG8d*6EDN7a;=dgVjYI--~4@Fe{{fcJ4B|;_Qg~&%6#?I(?X_$S4rDw{=>=8iZS=M^I#EF!m zXn%K_xXWwmm7R40LKXPo6ZzNZfN1-$S6RuVU=JlC|3#Xjo-%ebJvvC4n%IM)Q8NDh zGXd)L;ay_JMozc^mU*Uifnp=#+if>LD*O9MV#@wB1l``z|tlu(7PJqS6rm)0@ zJzP50{0Vpa`_?92oB;*i(?i225a6tZgT+9Dg?vTh)N4OKA~(c8{$8-ZKz=mb@$4IT9g8>;k11WIT+Y=%Z})`y#OJ zK-~rlEy!T%0h!Qo+jjPF2RQz2Z^B;dbvYg2JS`+@D~OWH{2-EEs^BdnuJskh>CKeT z1b;%8dU6QU%i@z?^6Q-{XESe^qRiw`ka+k!d-{c%&lXM}vCX^T=|?|;t6r?N*h-W4 z?o4Hy%BWqW+5=+md#5^8|49zjM zon_Do@rhzZ4XAb}-m|bMH$Vg<;^Bo6A8cfhUQ>|wFk~j(`>1NgD3sTg)He1pWrUj9WZ8R(Wn5Rr zhc&dXvv_m%HrwwHo9l_))NgdVUff%d&@4^$Pc=MDZdZ^xHL$KX^ z7W1{3UJ%>9v$W{Y3>vBvflE-soDj8{`>#F|8Z$EF%lN$NylORTn5JsI4mTMHWd*%- z2sD(RO(H-&i8&Ge)5i12slI5VekYCZ)s8rv&_)194;vKY2m8DIC2{4<&xTM3HHxwT zd(42n)gCJ$O4I|8sJq07#0U7Yk7PjPK&bMdy-5b)OdhSsBo^|IB_H43@&F@tpdJR0 z#~)=UJdP|=)O{0(rVZnjbTtwHV^}&kfLJQP@R6rda;K;O>9J9bnW$BgbzOZ8aO{D8 zPuJ%=Nqg~rdzk-IW0ZC5I%cc;ek5~=lDXl4?gMOQQ!KE5Aq$9qeGFM6jFP;Xy6)%N zjg{q(E6fnF02P3L*tutbHRR-gyYK3g^y9H?GMtIs;ojG zY~3*C>qD)(8jz}89w|xfb7L`^d>AG#%D-uq=qz}(o9kzzrx0LSBX90ykr*5oM+YmoTRWe+Cj6aq^xnWRymLmE>krCpoC9K%2LT0aK0Y< zt@kUUrrj1WL9rmBB8B;WXqg-BztOiUZX-!`*a&-75+!WZ!R0OPiZz?w`Of4q#+(;m z`${Ea6GnTCY3`V2R8w*}knf)*`RA@(8k{Lp4VP;<+ z9O_z0_{3=HcVi z5)&QGEB_&$)mu@)(Z8zuw#>Gc6C>^O-FUZEo;TO1@$>-xu%`v`tMS3V-8R1pb5w&zP%&rAP2*5h z$k{jqReFXCJhJ?-{x(2j5gH_zQ>;#Ec*@bUqF0u}XB09+U-K}+jQd>)k#AOkr6M8x zHyhrfJ`99@Vzr_B@*p@`DxeJ#`jimavZ9ZV%v{mO0!%9$TY(f%_}BU~3R%QxmSdD1 z2Bp45R0C=8qtx-~+oULrzCMHMof!&H<~~>BhOu9t%ti7ERzy&MfeFI`yIK^$C)AW3 zNQRoy0G}{Z0U#b~iYF^Jc^xOlG#4#C=;O>}m0(@{S^B2chkhuBA^ur)c`E;iGC9@z z7%fqif|WXh26-3;GTi8YpXUOSVWuR&C%jb}s5V4o;X~?V>XaR)8gBIQvmh3-xs)|E z8CExUnh>Ngjb^6YLgG<K?>j`V4Zp4G4%h8vUG^ouv)P!AnMkAWurg1zX2{E)hFp5ex ziBTDWLl+>ihx>1Um{+p<{v-zS?fx&Ioeu#9;aON_P4|J-J)gPF2-0?yt=+nHsn^1G z2bM#YbR1hHRbR9Or49U3T&x=1c0%dKX4HI!55MQv`3gt5ENVMAhhgEp@kG2k+qT|<5K~u`9G7x z?eB%b2B#mq)&K}m$lwDv|MU~=Y(D2jO{j*Box$GUn=$90z6O^7F?7pn=P;{r4C8qa zv1n*5N7uIvTn`8$>}(74>Oqk=E7){#pHUFd5XRJ5ObMhqODTa}=V0;+a(7JZR-4<3 zBTvsqRwLh?*ZF)JWsWOkEq7*XMQ!G3Rmkdh7ZbM#v1~?jt((e2y}u}Ky>1qa&Y7m@ zveIzH@?5Gexr79*?sbZGkVS;s1U<7D(%~7HjAmzj$aDYv_FGl5JX@LW8>w=HCDl6W z%?rsr0)bErYJ5G1v&zjr{8=lW)ZYcstgZAuL}!0~8HAcgOm@nJ9cvOOtL@)Fpl2Dr z8876Lt<|1eF88Jx#C*XyGI)C5z_o!Os!t=Xy0$Kj^4fG1pb@16%g z+<)zJ1n1QO78g#$3yHj+(Smv`HW5y_-PP{h2A1UXMG-c%hMvHLbF6t}G>KA)H# z`AWL~>8JUT(iq7;zJr!Aj)AS+n{mRbA3aM+Gj}b#PhHdTM_NkwQm330EC9waM$=slPfxR1vmr!vf~t_M?a%`@`&tdE}ipY-p#Q#zhLK zd9eFC;PjIEAKLkRkO94{rTuNFqKbNUGtaNZRRbax9;|%2WbnGu!44#64RriY5u0O} z05G^e&JB?Wb*8^g)aM`yt|}~QJkKCipFNeyex~P~SFPVEafD(73rncKmm)m~&`O*YUyY9z7tO%ec7z@wWcoOr-ebP z1k+|y?d{>1jLC=s4B2tEhiTtu->WVJno&%%6bG46KuU9D`GEN!C!9chM>zd=cl0+- z^k>4rpkq7_iWGHtBvy$Q`dja2;1ZdYmF6cANU6{v>l1=fSKRpsTRonp@alC%p{bhU z>g+(%-)&_nDQ~#bq5;xo^06RggA&uH4RMVb6wt;oQI+`m_zt>SiI5hXkfEnn6@ZNk zh9KUr1jtt6lBg$O#TAoTRvwUtWeMP3EjnGoRPQppiNF(sX%|Q4@kIjas|WZWXSENO zfF#2yOb;%XO*LeOoAwlf{u7_39$x(w3xT~)2BNJ2l5u4n3a0NkNLT4yT);7fA?1Vt zCz*`hbw-doYa09E!05zcfOT0EOORY``E@D z5{v%@F~&|UfNt@>vrj66W5f>jy+G_8&VB9D0*>N!7_Nr=-x6N?A)M8>1~q(X34sXp zpA%@w&c};L7u*G3;(Qe=LFL}NbTF$|aX#A%P(h`-N=ZRxCvlG$>Klv}jo0MS|UR8qKq-1FokBJmrbTJjQ!k#Is0tY+0c)m4Gp80YzYD zEGXd~ihaihk;?xUknXNH?rssjzaF+l6?HnDQjVP$i=q}{lp_WbOTKKg}HPKW)2sW`L#NvgmaY0^b2Ldk|t{P6{L{>ym;Xgao1PrudBgEMRFb^ zkPJ6v0h^tJ>K@;maHk_|6Z>yFzq@YvDOeO6Ob_?P4Ey>kHiJv`Wlh_MX4fBY36f%^ zV#2t;$Rg&}!Kwifm z;TVZXMxw3~$--{&A8-6vnUZ#s4`Z-zQ#+y7UI8#Hgsc|ompLUc zqlAG!Ti>t{JzYF^5pM925*PUWUvDuYDGKhC4FMx45c`L#V7%V+88@|khLj|V=J9Un zJEcP5qVCzR6p{FK!nIY~TXo)tJ!{>CG;~&u;EPlnNrwJ=5)ke@hJosN!siM$8b2mM zmc&weo-rY{n1+%c`c<{AT3i zjF{p253Ul-)s5A+!8Dp7?viXAdH1+qlY%mK5pp?{pS1t!3qmmDOq2TnoV`F3<>(XK z1=gfH39N_~8O+~({MZX~+QHyB>vtgwK0@uqGkX^eaf$UFHiO#>LB*7@=c0o6`0muj zmH00_F#p)s3E*$A-zP+p2bvXARTg3)Lxh`tf~9X>7!Z^kHV`uE%V9+BiBG=mxj*)M zr%3rn=)>GR`{#zmwD)$3ToLMx++uqsCx(+50Uk*5QJp2c6msxLD&P-y{c|XK6zZl3 z_Fgu8kp|gKVWv`GS!c56FWPO)ZrCCtYh#*yp-ssus)ot>_~UB zyGfjTjz#fXod{^KEQK1~@jN|;SZw5OgH#0wK78Oe4#vV3*|&XPQU z$r~5u8ziT0<#ICrX^<1){mvtaqT9OqlW?wiSu4X#rOC(0uL{Ownb%i1F_G&d>=l51 zx!FEO4_LK+)W^N6UF+fAccyyp{t)TE`;vF@1irbNjcXF8b?yFh zl5UEB>@;wO`~gMF!QB;h<``+f(lxAb_8B$;&vT7)(bXG(7x_5f%AZ5;h#3WjHisX{ zLTSguapAADXMwWZ&jsD0+K!+8#*6z7-(T+QUk>(~!Q|0&!d)PgEw8F6RK;LkB;!HXg79$+l*KU&-fRF|$o+kR4mJ36k9p&>*uS~RhCV+*Y$3U-k%~M)jxCFW zl9;bQ-fx4HPy)*(bhrKL!81M6*@6p5W?z*W`jb;@JKMFwmic{gQPv*) z?I{Fh)y)}(-6uh^I52xKo!LRZV0c*1X)Z(g+GVFN{2n%vD*@&IkVI{R_0;M28M z8vu?M+xVF-&<{l@1g{PA#hnyAq(gudz4WKSFL5YOr3q!|qrxa7z~F~rEJ29VQKgNe z1*L^m9&acg2p7&`u&V%oY|AKF(Xpv=)wf&j#n|;2UYEaUIHLJuTQw$SbrNn+)38PlfV^0<6s>)|hT#IAAS*T)_^_q@I} z0S%tV-HrXOjzkvW!YSbDjdH=g;=4A@whsDB zI8^aX6n=|ab(?!Ay!)CxH(wC(iX~Q@%FEx>C{Hmp98f2ku$Bsw%lk6v50(U@; zu68Z9U&za}O#-Mv^+!V=eyj6S)5oS{My`1MVs)nlnYl_$xU^QId1_jMf7&K8ij)jQ zJ|+~@l)xpV%~Y{P()$`+nBihkjE|3t3t8PoKU3wZ_Eg%0P<>%(A@oW#*8i$X!nfG& z;&&2ZIKlD~*Gff+p3A7QB!}Ei>RGhUUz^UoEpeJ{`2ov>wH!O@1$VW>A#D#{i2z9l z{d)FK9OYxRY#(6NUMO=q^5Ve7R|72%f}ZDlsm0BN&LzyaSHurXV4p5HGf7|Z)}8)g z5J#S6h{-+_U0m$k#+|N{6_8MYactWzWb+1~ea8wX3zX<@O0>pU*q($J{=R&7)P&jg z6Kb)o=HAnC_MP;cIeBq}{gG^0CZzOUJZ|7C-VjE}!?*UtKTcwwF33v^BYC&}Rq)C* zpAJ07-!{`flYX1@n;ZK-=x4)!o(%(1UqulVmes(D z^`_HNfM#umEYy~=zh$9&+?8$4!l(4rr?d#8hS4iks@9w%E4l`BKmhUtvsm1X-mKC3 z>4(u4yS45OgZIOQ;EQ6s`sjNelo!~mLe7gS69TW2WnFwEKcAwioq2mLXV<9CIa#(0`sQpl>vwW`A$D?!2%nt*HEb;Ga=o?92 zHAOICmXHEQ%Cc{m2>dLjPU1J}^w7zilFIxy9nG(OZbYPtW?3KJyv@A7|1A*NiD_v! zTLC}%E4kI*d?$lQBRL==MPsD#FyN0ZSr`;aeQ4C6a2INH9klU~_gCH;G2%8R4EuHb z44Ej^6301>?c06FP3X~xyP{77p`-3td;HKAGf4mZw1qRd6Z^^L#?qaiAKv~px)*jAV^re~beps9m{kJzb6n(oS8uCt#Lnjofg;Rl z=apY)JsV;^dVkzCW)jDrii_WTT`3iKri(xmCC1^AO}Vqt-1B*wwIlBAmE1AmdRtMc zD!fB@mtwHPHyV-^VIVU??*~*{olz-Ub)NCX941BDj_CKZ+QYQ?+``tyhy_7WFXF}_ z?~CVO#LsDYD!&}cph22{PZ*TK?$K^u`E7%{^na89Rm%!jSZs7vI-D zL1POD!1cu56G)*p1gui3-i^JZPX3tI*_Fq&JRwbz*#8LUSiMRWjuu`zD|uk;+X&d@ zuxF5C2{Zp#O?GtOB+R2~tF>MDI(}%p-W=M>1tEY}8E=b_l*WbOO zY9tCPgL3vMEqz)_eWeqmN{qobq_4)XdXJSe6Hj;Eie0??2ZZ?p;*_K8@(&v~1evu- zxQCA2YYvv@qhzamqdi`?{Z{c*7$arCdz4-4G(`O5It%y&8>d{#Y9Vax^FZ99ZK zUdIPpkNhp8uP3T+W4lhvUIYaoY##y6KtxBFoj3&5^@Q(^{677%C#3YJh$p-Ee2M6F ztJAoQv1N0L!|N8XBD(eAYcB#gRaIX7T8U5xXbx~cJSon~YnC zaJYE%zOj9y?E==_B$*9NiAm{~)2Z}t1$$l?qOYct5Ep5HvqFKvuSE7A5YF$K@2>UE zbQOdTNzjD#zS(L>wa2$K-WK!Pc%pY^8To58;^JaXZ}F30wuYl;WWs~rCoo&vrEtUh zTBLMU??yx1#;-weCPZyOJ%Yeb?14z+OXW0L_E+<)(q=;xz74U-Q~R~n*oC;MxyrJo(74r$y2t;x`D~{nhUw`N{Bbc zo`l5kb`Yy;L=&@MTQ~Ml_%V%){mCIj4WC}5q=A_ACx2^by!4w1rVX6H0ifayJsw;; z=+}5kjC?RG*q)^FA;udd?fK$7vU1x>y0w;A-)YbE%l$J%nRRjAIlrItFPgQvJ7Ytb z%HSFnjF2||X&L_g-Q>1{(mholW_-EJmSzsO%*VVVB4)#OAv<(kOIx2H!f)I9#e_Nyjdb$&*1KN^gM}yFIhi%%BWB}7Ke0M{0WY>CxJQUuL<9GW$I>S z8~;QmE{^wS?I`=DyV^l+MozMPWLoFz=uSLu99tiVHdCN>7jRs~vd13`&Gey!!7_+< z6o@25%!eN~+Eki#7iq@#{Hxl7pF0^`N;~p~#tc6HXJP0g5xvK|AuLSwNHVI2_Y-!& z4hemc%vOM5!ySDypyEGe=lAeFbIp`w8FIUcTqUwens>sTIV-jDhrcKGX7XHFXyazb z^DO8=ZgefY6R6&+)c1_i*WoenjtR5@_JU#Ph;4M8fpmznxE9R`=r@-#_y zkD?Muq|*gg7f*BQeI|Np#}Q|NXLJHM6GE{;SJn8ce`V1Gehym~{8c+M<2~=HcCRuk z-v&$8dc8YG+tK}NYVhwdm1iZ&A#r+T<>Ez88)Eq9j+G5h5D(_u{WQdUTOs+QbA(=? z{F6n6UV8D2*lvb)0vDrca$729KG$xO2aH$jWoWl0drlmefYsTswh)`GjMtmR=vEkJ zN$aTp_@@KL%KQ-VDB2ppbZK@X`6cJA5n`g>sbCTvU_xdid!{9gWA|>Mfs6rtHx6s` z_wMt*FgUTBZ@I2C62&zbs?pPvK9TpatkXzqDqe4YTr^nnQg8gWxjKt*s&eOMEp!Qc zG~PT`>xg76Xqh^dKI-Eu#K*VnvEf9qT{L0yNpVj)eVD#kQzGgVRbTB!5nWY=?t!cggiEGBAcWM2xNtW&9 zZB_6RZ}|a87CuEYRYCRJ`Sg+_gBK$_J@*zoWcJJw>eBw?G9WY(Jw~qN|A3MBR^~jm?>k5oGv7z+0jWOox(co@%nya|* zE-2peyX)#@svgwwDMPJ89dT=iO>}@wtNR@NUQ|cJZ};sX(w2uWP4AE5)@A ziJgy_TIZ+T&vG&xPh@Jmt!OJ|zA6C0ZxfF2 z7>aIZqecbmM$lyvDMwg2?Ipo9b)-WL6K_7(X_rmJgdd$-Qc^ywEw4SThChz6*_yu= z{v~a4V|RJtH-GThc2C0Z|JHPl{II-!?B~7cWnRz&dgP*UqoY!iCo&i-xeM}kl?ID* zKTX`w+;z0+MCdGcl{N?xb|tYb%Id=k++k_@(V%bTS&n09`0{S0)|>IH_F;V@_zrxS-dKDDc7+i`nHN8J z;38w69lzAS*WWa+dnVvk(0-KD3%*)TerLH zSCc}Tjc-mR5|1HAL$C1}oue|Qp&M!hmyDUcg)Cz>GXPEyeYf}+s48kIl*pL{{treP BIP(Ai literal 0 HcmV?d00001 diff --git a/packages/react-native-editor/android/app/src/main/res/values/strings.xml b/packages/react-native-editor/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000000000..29e2e4d13944c --- /dev/null +++ b/packages/react-native-editor/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Gutenberg + diff --git a/packages/react-native-editor/android/app/src/main/res/values/styles.xml b/packages/react-native-editor/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000000000..319eb0ca100b5 --- /dev/null +++ b/packages/react-native-editor/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/packages/react-native-editor/android/build.gradle b/packages/react-native-editor/android/build.gradle new file mode 100644 index 0000000000000..00832c500b3e4 --- /dev/null +++ b/packages/react-native-editor/android/build.gradle @@ -0,0 +1,39 @@ +buildscript { + ext { + gradlePluginVersion = '3.3.1' + kotlinVersion = '1.3.11' + buildToolsVersion = "28.0.3" + minSdkVersion = 21 + compileSdkVersion = 28 + targetSdkVersion = 28 + supportLibVersion = '28.0.0' + wordpressUtilsVersion = '1.22' + } + repositories { + google() + jcenter() + } + dependencies { + classpath "com.android.tools.build:gradle:$gradlePluginVersion" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" + } +} + +// Load the "build from source" flag value but default to `true` since this is the demo gutenberg-mobile project so +project.ext.buildGutenbergFromSource = project.properties.getOrDefault('wp.BUILD_GUTENBERG_FROM_SOURCE', true).toBoolean() + +allprojects { + repositories { + mavenLocal() + google() + jcenter() + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url "$rootDir/../node_modules/react-native/android" + } + maven { + // Local Maven repo containing AARs with JSC library built for Android + url "$rootDir/../node_modules/jsc-android/dist" + } + } +} diff --git a/packages/react-native-editor/android/gradle.properties b/packages/react-native-editor/android/gradle.properties new file mode 100644 index 0000000000000..915f0e66f9485 --- /dev/null +++ b/packages/react-native-editor/android/gradle.properties @@ -0,0 +1,20 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/packages/react-native-editor/android/gradle/wrapper/gradle-wrapper.jar b/packages/react-native-editor/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..01b8bf6b1f99cad9213fc495b33ad5bbab8efd20 GIT binary patch literal 54329 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

<5G2M1WZP5 zzqSuO91lJod(SBDDw<*sX(+F6Uq~YAeYV#2A;XQu_p=N5X+#cmu19Qk>QAnV=k!?wbk5I;tDWgFc}0NkvC*G=V+Yh1cyeJVq~9czZiDXe+S=VfL2g`LWo8om z$Y~FQc6MFjV-t1Y`^D9XMwY*U_re2R?&(O~68T&D4S{X`6JYU-pz=}ew-)V0AOUT1 zVOkHAB-8uBcRjLvz<9HS#a@X*Kc@|W)nyiSgi|u5$Md|P()%2(?olGg@ypoJwp6>m z*dnfjjWC>?_1p;%1brqZyDRR;8EntVA92EJ3ByOxj6a+bhPl z;a?m4rQAV1@QU^#M1HX)0+}A<7TCO`ZR_RzF}X9-M>cRLyN4C+lCk2)kT^3gN^`IT zNP~fAm(wyIoR+l^lQDA(e1Yv}&$I!n?&*p6?lZcQ+vGLLd~fM)qt}wsbf3r=tmVYe zl)ntf#E!P7wlakP9MXS7m0nsAmqxZ*)#j;M&0De`oNmFgi$ov#!`6^4)iQyxg5Iuj zjLAhzQ)r`^hf7`*1`Rh`X;LVBtDSz@0T?kkT1o!ijeyTGt5vc^Cd*tmNgiNo^EaWvaC8$e+nb_{W01j3%=1Y&92YacjCi>eNbwk%-gPQ@H-+4xskQ}f_c=jg^S-# zYFBDf)2?@5cy@^@FHK5$YdAK9cI;!?Jgd}25lOW%xbCJ>By3=HiK@1EM+I46A)Lsd zeT|ZH;KlCml=@;5+hfYf>QNOr^XNH%J-lvev)$Omy8MZ`!{`j>(J5cG&ZXXgv)TaF zg;cz99i$4CX_@3MIb?GL0s*8J=3`#P(jXF(_(6DXZjc@(@h&=M&JG)9&Te1?(^XMW zjjC_70|b=9hB6pKQi`S^Ls7JyJw^@P>Ko^&q8F&?>6i;#CbxUiLz1ZH4lNyd@QACd zu>{!sqjB!2Dg}pbAXD>d!3jW}=5aN0b;rw*W>*PAxm7D)aw(c*RX2@bTGEI|RRp}vw7;NR2wa;rXN{L{Q#=Fa z$x@ms6pqb>!8AuV(prv>|aU8oWV={C&$c zMa=p=CDNOC2tISZcd8~18GN5oTbKY+Vrq;3_obJlfSKRMk;Hdp1`y`&LNSOqeauR_ z^j*Ojl3Ohzb5-a49A8s|UnM*NM8tg}BJXdci5%h&;$afbmRpN0&~9rCnBA`#lG!p zc{(9Y?A0Y9yo?wSYn>iigf~KP$0*@bGZ>*YM4&D;@{<%Gg5^uUJGRrV4 z(aZOGB&{_0f*O=Oi0k{@8vN^BU>s3jJRS&CJOl3o|BE{FAA&a#2YYiX3pZz@|Go-F z|Fly;7eX2OTs>R}<`4RwpHFs9nwh)B28*o5qK1Ge=_^w0m`uJOv!=&!tzt#Save(C zgKU=Bsgql|`ui(e1KVxR`?>Dx>(rD1$iWp&m`v)3A!j5(6vBm*z|aKm*T*)mo(W;R zNGo2`KM!^SS7+*9YxTm6YMm_oSrLceqN*nDOAtagULuZl5Q<7mOnB@Hq&P|#9y{5B z!2x+2s<%Cv2Aa0+u{bjZXS);#IFPk(Ph-K7K?3i|4ro> zRbqJoiOEYo(Im^((r}U4b8nvo_>4<`)ut`24?ILnglT;Pd&U}$lV3U$F9#PD(O=yV zgNNA=GW|(E=&m_1;uaNmipQe?pon4{T=zK!N!2_CJL0E*R^XXIKf*wi!>@l}3_P9Z zF~JyMbW!+n-+>!u=A1ESxzkJy$DRuG+$oioG7(@Et|xVbJ#BCt;J43Nvj@MKvTxzy zMmjNuc#LXBxFAwIGZJk~^!q$*`FME}yKE8d1f5Mp}KHNq(@=Z8YxV}0@;YS~|SpGg$_jG7>_8WWYcVx#4SxpzlV9N4aO>K{c z$P?a_fyDzGX$Of3@ykvedGd<@-R;M^Shlj*SswJLD+j@hi_&_>6WZ}#AYLR0iWMK|A zH_NBeu(tMyG=6VO-=Pb>-Q#$F*or}KmEGg*-n?vWQREURdB#+6AvOj*I%!R-4E_2$ zU5n9m>RWs|Wr;h2DaO&mFBdDb-Z{APGQx$(L`if?C|njd*fC=rTS%{o69U|meRvu?N;Z|Y zbT|ojL>j;q*?xXmnHH#3R4O-59NV1j=uapkK7}6@Wo*^Nd#(;$iuGsb;H315xh3pl zHaJ>h-_$hdNl{+|Zb%DZH%ES;*P*v0#}g|vrKm9;j-9e1M4qX@zkl&5OiwnCz=tb6 zz<6HXD+rGIVpGtkb{Q^LIgExOm zz?I|oO9)!BOLW#krLmWvX5(k!h{i>ots*EhpvAE;06K|u_c~y{#b|UxQ*O@Ks=bca z^_F0a@61j3I(Ziv{xLb8AXQj3;R{f_l6a#H5ukg5rxwF9A$?Qp-Mo54`N-SKc}fWp z0T)-L@V$$&my;l#Ha{O@!fK4-FSA)L&3<${Hcwa7ue`=f&YsXY(NgeDU#sRlT3+9J z6;(^(sjSK@3?oMo$%L-nqy*E;3pb0nZLx6 z;h5)T$y8GXK1DS-F@bGun8|J(v-9o=42&nLJy#}M5D0T^5VWBNn$RpC zZzG6Bt66VY4_?W=PX$DMpKAI!d`INr) zkMB{XPQ<52rvWVQqgI0OL_NWxoe`xxw&X8yVftdODPj5|t}S6*VMqN$-h9)1MBe0N zYq?g0+e8fJCoAksr0af1)FYtz?Me!Cxn`gUx&|T;)695GG6HF7!Kg1zzRf_{VWv^bo81v4$?F6u2g|wxHc6eJQAg&V z#%0DnWm2Rmu71rPJ8#xFUNFC*V{+N_qqFH@gYRLZ6C?GAcVRi>^n3zQxORPG)$-B~ z%_oB?-%Zf7d*Fe;cf%tQwcGv2S?rD$Z&>QC2X^vwYjnr5pa5u#38cHCt4G3|efuci z@3z=#A13`+ztmp;%zjXwPY_aq-;isu*hecWWX_=Z8paSqq7;XYnUjK*T>c4~PR4W7 z#C*%_H&tfGx`Y$w7`dXvVhmovDnT>btmy~SLf>>~84jkoQ%cv=MMb+a{JV&t0+1`I z32g_Y@yDhKe|K^PevP~MiiVl{Ou7^Mt9{lOnXEQ`xY^6L8D$705GON{!1?1&YJEl#fTf5Z)da=yiEQ zGgtC-soFGOEBEB~ZF_{7b(76En>d}mI~XIwNw{e>=Fv)sgcw@qOsykWr?+qAOZSVrQfg}TNI ztKNG)1SRrAt6#Q?(me%)>&A_^DM`pL>J{2xu>xa$3d@90xR61TQDl@fu%_85DuUUA za9tn64?At;{`BAW6oykwntxHeDpXsV#{tmt5RqdN7LtcF4vR~_kZNT|wqyR#z^Xcd zFdymVRZvyLfTpBT>w9<)Ozv@;Yk@dOSVWbbtm^y@@C>?flP^EgQPAwsy75bveo=}T zFxl(f)s)j(0#N_>Or(xEuV(n$M+`#;Pc$1@OjXEJZumkaekVqgP_i}p`oTx;terTx zZpT+0dpUya2hqlf`SpXN{}>PfhajNk_J0`H|2<5E;U5Vh4F8er z;RxLSFgpGhkU>W?IwdW~NZTyOBrQ84H7_?gviIf71l`EETodG9a1!8e{jW?DpwjL? zGEM&eCzwoZt^P*8KHZ$B<%{I}>46IT%jJ3AnnB5P%D2E2Z_ z1M!vr#8r}1|KTqWA4%67ZdbMW2YJ81b(KF&SQ2L1Qn(y-=J${p?xLMx3W7*MK;LFQ z6Z`aU;;mTL4XrrE;HY*Rkh6N%?qviUGNAKiCB~!P}Z->IpO6E(gGd7I#eDuT7j|?nZ zK}I(EJ>$Kb&@338M~O+em9(L!+=0zBR;JAQesx|3?Ok90)D1aS9P?yTh6Poh8Cr4X zk3zc=f2rE7jj+aP7nUsr@~?^EGP>Q>h#NHS?F{Cn`g-gD<8F&dqOh-0sa%pfL`b+1 zUsF*4a~)KGb4te&K0}bE>z3yb8% zibb5Q%Sfiv7feb1r0tfmiMv z@^4XYwg@KZI=;`wC)`1jUA9Kv{HKe2t$WmRcR4y8)VAFjRi zaz&O7Y2tDmc5+SX(bj6yGHYk$dBkWc96u3u&F)2yEE~*i0F%t9Kg^L6MJSb&?wrXi zGSc;_rln$!^ybwYBeacEFRsVGq-&4uC{F)*Y;<0y7~USXswMo>j4?~5%Zm!m@i@-> zXzi82sa-vpU{6MFRktJy+E0j#w`f`>Lbog{zP|9~hg(r{RCa!uGe>Yl536cn$;ouH za#@8XMvS-kddc1`!1LVq;h57~zV`7IYR}pp3u!JtE6Q67 zq3H9ZUcWPm2V4IukS}MCHSdF0qg2@~ufNx9+VMjQP&exiG_u9TZAeAEj*jw($G)zL zq9%#v{wVyOAC4A~AF=dPX|M}MZV)s(qI9@aIK?Pe+~ch|>QYb+78lDF*Nxz2-vpRbtQ*F4$0fDbvNM#CCatgQ@z1+EZWrt z2dZfywXkiW=no5jus-92>gXn5rFQ-COvKyegmL=4+NPzw6o@a?wGE-1Bt;pCHe;34K%Z z-FnOb%!nH;)gX+!a3nCk?5(f1HaWZBMmmC@lc({dUah+E;NOros{?ui1zPC-Q0);w zEbJmdE$oU$AVGQPdm{?xxI_0CKNG$LbY*i?YRQ$(&;NiA#h@DCxC(U@AJ$Yt}}^xt-EC_ z4!;QlLkjvSOhdx!bR~W|Ezmuf6A#@T`2tsjkr>TvW*lFCMY>Na_v8+{Y|=MCu1P8y z89vPiH5+CKcG-5lzk0oY>~aJC_0+4rS@c@ZVKLAp`G-sJB$$)^4*A!B zmcf}lIw|VxV9NSoJ8Ag3CwN&d7`|@>&B|l9G8tXT^BDHOUPrtC70NgwN4${$k~d_4 zJ@eo6%YQnOgq$th?0{h`KnqYa$Nz@vlHw<%!C5du6<*j1nwquk=uY}B8r7f|lY+v7 zm|JU$US08ugor8E$h3wH$c&i~;guC|3-tqJy#T;v(g( zBZtPMSyv%jzf->435yM(-UfyHq_D=6;ouL4!ZoD+xI5uCM5ay2m)RPmm$I}h>()hS zO!0gzMxc`BPkUZ)WXaXam%1;)gedA7SM8~8yIy@6TPg!hR0=T>4$Zxd)j&P-pXeSF z9W`lg6@~YDhd19B9ETv(%er^Xp8Yj@AuFVR_8t*KS;6VHkEDKI#!@l!l3v6`W1`1~ zP{C@keuV4Q`Rjc08lx?zmT$e$!3esc9&$XZf4nRL(Z*@keUbk!GZi(2Bmyq*saOD? z3Q$V<*P-X1p2}aQmuMw9nSMbOzuASsxten7DKd6A@ftZ=NhJ(0IM|Jr<91uAul4JR zADqY^AOVT3a(NIxg|U;fyc#ZnSzw2cr}#a5lZ38>nP{05D)7~ad7JPhw!LqOwATXtRhK!w0X4HgS1i<%AxbFmGJx9?sEURV+S{k~g zGYF$IWSlQonq6}e;B(X(sIH|;52+(LYW}v_gBcp|x%rEAVB`5LXg_d5{Q5tMDu0_2 z|LOm$@K2?lrLNF=mr%YP|U-t)~9bqd+wHb4KuPmNK<}PK6e@aosGZK57=Zt+kcszVOSbe;`E^dN! ze7`ha3WUUU7(nS0{?@!}{0+-VO4A{7+nL~UOPW9_P(6^GL0h${SLtqG!} zKl~Ng5#@Sy?65wk9z*3SA`Dpd4b4T^@C8Fhd8O)k_4%0RZL5?#b~jmgU+0|DB%0Z) zql-cPC>A9HPjdOTpPC` zQwvF}uB5kG$Xr4XnaH#ruSjM*xG?_hT7y3G+8Ox`flzU^QIgb_>2&-f+XB6MDr-na zSi#S+c!ToK84<&m6sCiGTd^8pNdXo+$3^l3FL_E`0 z>8it5YIDxtTp2Tm(?}FX^w{fbfgh7>^8mtvN>9fWgFN_*a1P`Gz*dyOZF{OV7BC#j zQV=FQM5m>47xXgapI$WbPM5V`V<7J9tD)oz@d~MDoM`R^Y6-Na(lO~uvZlpu?;zw6 zVO1faor3dg#JEb5Q*gz4<W8tgC3nE2BG2jeIQs1)<{In&7hJ39x=;ih;CJDy)>0S1at*7n?Wr0ahYCpFjZ|@u91Zl7( zv;CSBRC65-6f+*JPf4p1UZ)k=XivKTX6_bWT~7V#rq0Xjas6hMO!HJN8GdpBKg_$B zwDHJF6;z?h<;GXFZan8W{XFNPpOj!(&I1`&kWO86p?Xz`a$`7qV7Xqev|7nn_lQuX ziGpU1MMYt&5dE2A62iX3;*0WzNB9*nSTzI%62A+N?f?;S>N@8M=|ef3gtQTIA*=yq zQAAjOqa!CkHOQo4?TsqrrsJLclXcP?dlAVv?v`}YUjo1Htt;6djP@NPFH+&p1I+f_ z)Y279{7OWomY8baT(4TAOlz1OyD{4P?(DGv3XyJTA2IXe=kqD)^h(@*E3{I~w;ws8 z)ZWv7E)pbEM zd3MOXRH3mQhks9 zv6{s;k0y5vrcjXaVfw8^>YyPo=oIqd5IGI{)+TZq5Z5O&hXAw%ZlL}^6FugH;-%vP zAaKFtt3i^ag226=f0YjzdPn6|4(C2sC5wHFX{7QF!tG1E-JFA`>eZ`}$ymcRJK?0c zN363o{&ir)QySOFY0vcu6)kX#;l??|7o{HBDVJN+17rt|w3;(C_1b>d;g9Gp=8YVl zYTtA52@!7AUEkTm@P&h#eg+F*lR zQ7iotZTcMR1frJ0*V@Hw__~CL>_~2H2cCtuzYIUD24=Cv!1j6s{QS!v=PzwQ(a0HS zBKx04KA}-Ue+%9d`?PG*hIij@54RDSQpA7|>qYVIrK_G6%6;#ZkR}NjUgmGju)2F`>|WJoljo)DJgZr4eo1k1i1+o z1D{>^RlpIY8OUaOEf5EBu%a&~c5aWnqM zxBpJq98f=%M^{4mm~5`CWl%)nFR64U{(chmST&2jp+-r z3675V<;Qi-kJud%oWnCLdaU-)xTnMM%rx%Jw6v@=J|Ir=4n-1Z23r-EVf91CGMGNz zb~wyv4V{H-hkr3j3WbGnComiqmS0vn?n?5v2`Vi>{Ip3OZUEPN7N8XeUtF)Ry6>y> zvn0BTLCiqGroFu|m2zG-;Xb6;W`UyLw)@v}H&(M}XCEVXZQoWF=Ykr5lX3XWwyNyF z#jHv)A*L~2BZ4lX?AlN3X#axMwOC)PoVy^6lCGse9bkGjb=qz%kDa6}MOmSwK`cVO zt(e*MW-x}XtU?GY5}9{MKhRhYOlLhJE5=ca+-RmO04^ z66z{40J=s=ey9OCdc(RCzy zd7Zr1%!y3}MG(D=wM_ebhXnJ@MLi7cImDkhm0y{d-Vm81j`0mbi4lF=eirlr)oW~a zCd?26&j^m4AeXEsIUXiTal)+SPM4)HX%%YWF1?(FV47BaA`h9m67S9x>hWMVHx~Hg z1meUYoLL(p@b3?x|9DgWeI|AJ`Ia84*P{Mb%H$ZRROouR4wZhOPX15=KiBMHl!^JnCt$Az`KiH^_d>cev&f zaG2>cWf$=A@&GP~DubsgYb|L~o)cn5h%2`i^!2)bzOTw2UR!>q5^r&2Vy}JaWFUQE04v>2;Z@ZPwXr?y&G(B^@&y zsd6kC=hHdKV>!NDLIj+3rgZJ|dF`%N$DNd;B)9BbiT9Ju^Wt%%u}SvfM^=|q-nxDG zuWCQG9e#~Q5cyf8@y76#kkR^}{c<_KnZ0QsZcAT|YLRo~&tU|N@BjxOuy`#>`X~Q< z?R?-Gsk$$!oo(BveQLlUrcL#eirhgBLh`qHEMg`+sR1`A=1QX7)ZLMRT+GBy?&mM8 zQG^z-!Oa&J-k7I(3_2#Q6Bg=NX<|@X&+YMIOzfEO2$6Mnh}YV!m!e^__{W@-CTprr zbdh3f=BeCD$gHwCrmwgM3LAv3!Mh$wM)~KWzp^w)Cu6roO7uUG5z*}i0_0j47}pK; ztN530`ScGatLOL06~zO)Qmuv`h!gq5l#wx(EliKe&rz-5qH(hb1*fB#B+q`9=jLp@ zOa2)>JTl7ovxMbrif`Xe9;+fqB1K#l=Dv!iT;xF zdkCvS>C5q|O;}ns3AgoE({Ua-zNT-9_5|P0iANmC6O76Sq_(AN?UeEQJ>#b54fi3k zFmh+P%b1x3^)0M;QxXLP!BZ^h|AhOde*{9A=f3|Xq*JAs^Y{eViF|=EBfS6L%k4ip zk+7M$gEKI3?bQg?H3zaE@;cyv9kv;cqK$VxQbFEsy^iM{XXW0@2|DOu$!-k zSFl}Y=jt-VaT>Cx*KQnHTyXt}f9XswFB9ibYh+k2J!ofO+nD?1iw@mwtrqI4_i?nE zhLkPp41ED62me}J<`3RN80#vjW;wt`pP?%oQ!oqy7`miL>d-35a=qotK$p{IzeSk# ze_$CFYp_zIkrPFVaW^s#U4xT1lI^A0IBe~Y<4uS%zSV=wcuLr%gQT=&5$&K*bwqx| zWzCMiz>7t^Et@9CRUm9E+@hy~sBpm9fri$sE1zgLU((1?Yg{N1Sars=DiW&~Zw=3I zi7y)&oTC?UWD2w97xQ&5vx zRXEBGeJ(I?Y}eR0_O{$~)bMJRTsNUPIfR!xU9PE7A>AMNr_wbrFK>&vVw=Y;RH zO$mlpmMsQ}-FQ2cSj7s7GpC+~^Q~dC?y>M}%!-3kq(F3hGWo9B-Gn02AwUgJ>Z-pKOaj zysJBQx{1>Va=*e@sLb2z&RmQ7ira;aBijM-xQ&cpR>X3wP^foXM~u1>sv9xOjzZpX z0K;EGouSYD~oQ&lAafj3~EaXfFShC+>VsRlEMa9cg9i zFxhCKO}K0ax6g4@DEA?dg{mo>s+~RPI^ybb^u--^nTF>**0l5R9pocwB?_K)BG_)S zyLb&k%XZhBVr7U$wlhMqwL)_r&&n%*N$}~qijbkfM|dIWP{MyLx}X&}ES?}7i;9bW zmTVK@zR)7kE2+L42Q`n4m0VVg5l5(W`SC9HsfrLZ=v%lpef=Gj)W59VTLe+Z$8T8i z4V%5+T0t8LnM&H>Rsm5C%qpWBFqgTwL{=_4mE{S3EnBXknM&u8n}A^IIM4$s3m(Rd z>zq=CP-!9p9es2C*)_hoL@tDYABn+o#*l;6@7;knWIyDrt5EuakO99S$}n((Fj4y} zD!VvuRzghcE{!s;jC*<_H$y6!6QpePo2A3ZbX*ZzRnQq*b%KK^NF^z96CHaWmzU@f z#j;y?X=UP&+YS3kZx7;{ zDA{9(wfz7GF`1A6iB6fnXu0?&d|^p|6)%3$aG0Uor~8o? z*e}u#qz7Ri?8Uxp4m_u{a@%bztvz-BzewR6bh*1Xp+G=tQGpcy|4V_&*aOqu|32CM zz3r*E8o8SNea2hYJpLQ-_}R&M9^%@AMx&`1H8aDx4j%-gE+baf2+9zI*+Pmt+v{39 zDZ3Ix_vPYSc;Y;yn68kW4CG>PE5RoaV0n@#eVmk?p$u&Fy&KDTy!f^Hy6&^-H*)#u zdrSCTJPJw?(hLf56%2;_3n|ujUSJOU8VPOTlDULwt0jS@j^t1WS z!n7dZIoT+|O9hFUUMbID4Ec$!cc($DuQWkocVRcYSikFeM&RZ=?BW)mG4?fh#)KVG zcJ!<=-8{&MdE)+}?C8s{k@l49I|Zwswy^ZN3;E!FKyglY~Aq?4m74P-0)sMTGXqd5(S<-(DjjM z&7dL-Mr8jhUCAG$5^mI<|%`;JI5FVUnNj!VO2?Jiqa|c2;4^n!R z`5KK0hyB*F4w%cJ@Un6GC{mY&r%g`OX|1w2$B7wxu97%<@~9>NlXYd9RMF2UM>(z0 zouu4*+u+1*k;+nFPk%ly!nuMBgH4sL5Z`@Rok&?Ef=JrTmvBAS1h?C0)ty5+yEFRz zY$G=coQtNmT@1O5uk#_MQM1&bPPnspy5#>=_7%WcEL*n$;t3FUcXxMpcXxMpA@1(( z32}FUxI1xoH;5;M_i@j?f6mF_p3Cd1DTb=dTK#qJneN`*d+pvYD*L?M(1O%DEmB>$ zs6n;@Lcm9c7=l6J&J(yBnm#+MxMvd-VKqae7;H7p-th(nwc}?ov%$8ckwY%n{RAF3 zTl^SF7qIWdSa7%WJ@B^V-wD|Z)9IQkl$xF>ebi>0AwBv5oh5$D*C*Pyj?j_*pT*IMgu3 z$p#f0_da0~Wq(H~yP##oQ}x66iYFc0O@JFgyB>ul@qz{&<14#Jy@myMM^N%oy0r|b zDPBoU!Y$vUxi%_kPeb4Hrc>;Zd^sftawKla0o|3mk@B)339@&p6inAo(Su3qlK2a) zf?EU`oSg^?f`?y=@Vaq4Dps8HLHW zIe~fHkXwT>@)r+5W7#pW$gzbbaJ$9e;W-u#VF?D=gsFfFlBJ5wR>SB;+f)sFJsYJ| z29l2Ykg+#1|INd=uj3&d)m@usb;VbGnoI1RHvva@?i&>sP&;Lt!ZY=e!=d-yZ;QV% zP@(f)+{|<*XDq%mvYKwIazn8HS`~mW%9+B|`&x*n?Y$@l{uy@ z^XxQnuny+p0JG0h)#^7}C|Btyp7=P#A2ed1vP0KGw9+~-^y4~S$bRm3gCT{+7Z<(A zJ&tg=7X|uKPKd6%z@IcZ@FgQe=rS&&1|O!s#>B_z!M_^B`O(SqE>|x- zh{~)$RW_~jXj)}mO>_PZvGdD|vtN44=Tp!oCP0>)gYeJ;n*&^BZG{$>y%Yb|L zeBUI#470!F`GM-U$?+~k+g9lj5C-P_i1%c3Zbo!@EjMJDoxQ7%jHHKeMVw&_(aoL? z%*h*aIt9-De$J>ZRLa7aWcLn<=%D+u0}RV9ys#TBGLAE%Vh`LWjWUi`Q3kpW;bd)YD~f(#$jfNdx}lOAq=#J*aV zz;K>I?)4feI+HrrrhDVkjePq;L7r87;&vm|7qaN z_>XhM8GU6I5tSr3O2W4W%m6wDH#=l32!%LRho(~*d3GfA6v-ND^0trp-qZs(B(ewD z3y3@ZV!2`DZ6b6c(Ftqg-s715;=lZqGF>H+z+c&7NeDz!We+7WNk>X*b7OZmlcTnf z{C1CB67e@xbWprDhN+t!B%4od#|>yQA$5mBM>XdhP?1U^%aD&^=PYWQEY*8Mr%h~R zOVzrd9}6RSl}Lt42r166_*s|U<1}`{l(H}m8H=D+oG>*=+=W^%IMB&CHZ-?)78G2b z)9kj_ldMecB_65eV&R+(yQ$2`ol&&7$&ns_{%A6cC2C*C6dY7qyWrHSYyOBl$0=$> z-YgkNlH{1MR-FXx7rD=4;l%6Ub3OMx9)A|Y7KLnvb`5OB?hLb#o@Wu(k|;_b!fbq( zX|rh*D3ICnZF{5ipmz8`5UV3Otwcso0I#;Q(@w+Pyj&Qa(}Uq2O(AcLU(T`+x_&~?CFLly*`fdP6NU5A|ygPXM>}(+) zkTRUw*cD<% zzFnMeB(A4A9{|Zx2*#!sRCFTk2|AMy5+@z8ws0L-{mt(9;H#}EGePUWxLabB_fFcp zLiT)TDLUXPbV2$Cde<9gv4=;u5aQ$kc9|GE2?AQZsS~D%AR`}qP?-kS_bd>C2r(I; zOc&r~HB7tUOQgZOpH&7C&q%N612f?t(MAe(B z@A!iZi)0qo^Nyb`#9DkzKjoI4rR1ghi1wJU5Tejt!ISGE93m@qDNYd|gg9(s|8-&G zcMnsX0=@2qQQ__ujux#EJ=veg&?3U<`tIWk~F=vm+WTviUvueFk&J@TcoGO{~C%6NiiNJ*0FJBQ!3Ab zm59ILI24e8!=;-k%yEf~YqN_UJ8k z0GVIS0n^8Yc)UK1eQne}<0XqzHkkTl*8VrWr zo}y?WN5@TL*1p>@MrUtxq0Vki($sn_!&;gR2e$?F4^pe@J_BQS&K3{4n+f7tZX4wQn z*Z#0eBs&H8_t`w^?ZYx=BGgyUI;H$i*t%(~8BRZ4gH+nJT0R-3lzdn4JY=xfs!YpF zQdi3kV|NTMB}uxx^KP!`=S(}{s*kfb?6w^OZpU?Wa~7f@Q^pV}+L@9kfDE`c@h5T* zY@@@?HJI)j;Y#l8z|k8y#lNTh2r?s=X_!+jny>OsA7NM~(rh3Tj7?e&pD!Jm28*UL zmRgopf0sV~MzaHDTW!bPMNcymg=!OS2bD@6Z+)R#227ET3s+2m-(W$xXBE#L$Whsi zjz6P+4cGBQkJY*vc1voifsTD}?H$&NoN^<=zK~75d|WSU4Jaw`!GoPr$b>4AjbMy+ z%4;Kt7#wwi)gyzL$R97(N?-cKygLClUk{bBPjSMLdm|MG-;oz70mGNDus zdGOi}L59=uz=VR2nIux^(D85f)1|tK&c!z1KS6tgYd^jgg6lT^5h42tZCn#Q-9k>H zVby-zby2o_GjI!zKn8ZuQ`asmp6R@=FR9kJ_Vja#I#=wtQWTes>INZynAoj$5 zN^9Ws&hvDhu*lY=De$Zby12$N&1#U2W1OHzuh;fSZH4igQodAG1K*;%>P9emF7PPD z>XZ&_hiFcX9rBXQ8-#bgSQ!5coh=(>^8gL%iOnnR>{_O#bF>l+6yZQ4R42{Sd#c7G zHy!)|g^tmtT4$YEk9PUIM8h)r?0_f=aam-`koGL&0Zp*c3H2SvrSr60s|0VtFPF^) z-$}3C94MKB)r#398;v@)bMN#qH}-%XAyJ_V&k@k+GHJ^+YA<*xmxN8qT6xd+3@i$( z0`?f(la@NGP*H0PT#Od3C6>0hxarvSr3G;0P=rG^v=nB5sfJ}9&klYZ>G1BM2({El zg0i|%d~|f2e(yWsh%r)XsV~Fm`F*Gsm;yTQV)dW!c8^WHRfk~@iC$w^h=ICTD!DD;~TIlIoVUh*r@aS|%Ae3Io zU~>^l$P8{6Ro~g26!@NToOZ(^5f8p`*6ovpcQdIDf%)?{NPPwHB>l*f_prp9XDCM8 zG`(I8xl|w{x(c`}T_;LJ!%h6L=N=zglX2Ea+2%Q8^GA>jow-M>0w{XIE-yz|?~M+; zeZO2F3QK@>(rqR|i7J^!1YGH^9MK~IQPD}R<6^~VZWErnek^xHV>ZdiPc4wesiYVL z2~8l7^g)X$kd}HC74!Y=Uq^xre22Osz!|W@zsoB9dT;2Dx8iSuK!Tj+Pgy0-TGd)7 zNy)m@P3Le@AyO*@Z2~+K9t2;=7>-*e(ZG`dBPAnZLhl^zBIy9G+c)=lq0UUNV4+N% zu*Nc4_cDh$ou3}Re}`U&(e^N?I_T~#42li13_LDYm`bNLC~>z0ZG^o6=IDdbIf+XFTfe>SeLw4UzaK#4CM4HNOs- zz>VBRkL@*A7+XY8%De)|BYE<%pe~JzZN-EU4-s_P9eINA^Qvy3z?DOTlkS!kfBG_7 zg{L6N2(=3y=iY)kang=0jClzAWZqf+fDMy-MH&Px&6X36P^!0gj%Z0JLvg~oB$9Z| zgl=6_$4LSD#(2t{Eg=2|v_{w7op+)>ehcvio@*>XM!kz+xfJees9(ObmZ~rVGH>K zWaiBlWGEV{JU=KQ>{!0+EDe-+Z#pO zv{^R<7A^gloN;Tx$g`N*Z5OG!5gN^Xj=2<4D;k1QuN5N{4O`Pfjo3Ht_RRYSzsnhTK?YUf)z4WjNY z>R04WTIh4N(RbY*hPsjKGhKu;&WI)D53RhTUOT}#QBDfUh%lJSy88oqBFX)1pt>;M z>{NTkPPk8#}DUO;#AV8I7ZQsC?Wzxn|3ubiQYI|Fn_g4r)%eNZ~ zSvTYKS*9Bcw{!=C$=1` zGQ~1D97;N!8rzKPX5WoqDHosZIKjc!MS+Q9ItJK?6Wd%STS2H!*A#a4t5 zJ-Rz_`n>>Up%|81tJR2KND<6Uoe82l={J~r*D5c_bThxVxJ<}?b0Sy}L1u|Yk=e&t z0b5c2X(#x^^fI)l<2=3b=|1OH_)-2beVEH9IzpS*Es0!4Or+xE$%zdgY+VTK2}#fpxSPtD^1a6Z)S%5eqVDzs`rL1U;Zep@^Y zWf#dJzp_iWP{z=UEepfZ4ltYMb^%H7_m4Pu81CP@Ra)ds+|Oi~a>Xi(RBCy2dTu-R z$dw(E?$QJUA3tTIf;uZq!^?_edu~bltHs!5WPM-U=R74UsBwN&nus2c?`XAzNUYY|fasp?z$nFwXQYnT`iSR<=N`1~h3#L#lF-Fc1D#UZhC2IXZ{#IDYl_r8 z?+BRvo_fPGAXi+bPVzp=nKTvN_v*xCrb^n=3cQ~No{JzfPo@YWh=7K(M_$Jk*+9u* zEY4Ww3A|JQ`+$z(hec&3&3wxV{q>D{fj!Euy2>tla^LP_2T8`St2em~qQp zm{Tk<>V3ecaP1ghn}kzS7VtKksV*27X+;Y6#I$urr=25xuC=AIP7#Jp+)L67G6>EZ zA~n}qEWm6A8GOK!3q9Yw*Z07R(qr{YBOo5&4#pD_O(O^y0a{UlC6w@ZalAN0Rq_E0 zVA!pI-6^`?nb7`y(3W5OsoVJ^MT!7r57Jm{FS{(GWAWwAh$dBpffjcOZUpPv$tTc} zv~jnA{+|18GmMDq7VK6Sb=-2nzz^7TDiixA{mf%8eQC|x>*=)((3}twJCoh~V4m3) zM5fwDbrTpnYR`lIO7Il7Eq@)St{h>Nllv+5Hk2FAE8fdD*YT|zJix?!cZ-=Uqqieb z-~swMc+yvTu(h?fT4K_UuVDqTup3%((3Q!0*Tfwyl`3e27*p{$ zaJMMF-Pb=3imlQ*%M6q5dh3tT+^%wG_r)q5?yHvrYAmc-zUo*HtP&qP#@bfcX~jwn!$k~XyC#Ox9i7dO7b4}b^f zrVEPkeD%)l0-c_gazzFf=__#Q6Pwv_V=B^h=)CYCUszS6g!}T!r&pL)E*+2C z5KCcctx6Otpf@x~7wZz*>qB_JwO!uI@9wL0_F>QAtg3fvwj*#_AKvsaD?!gcj+zp) zl2mC)yiuumO+?R2`iiVpf_E|9&}83;^&95y96F6T#E1}DY!|^IW|pf-3G0l zE&_r{24TQAa`1xj3JMev)B_J-K2MTo{nyRKWjV#+O}2ah2DZ>qnYF_O{a6Gy{aLJi#hWo3YT3U7yVxoNrUyw31163sHsCUQG|rriZFeoTcP` zFV<&;-;5x0n`rqMjx2^_7y)dHPV@tJC*jHQo!~1h`#z)Gu7m@0@z*e?o|S#5#Ht~%GC|r zd?EY_E0XKUQ2o7*e3D9{Lt7s#x~`hjzwQ{TYw;Fq8la&)%4Vj_N@ivmaSNw9X3M$MAG97a&m1SODLZ-#$~7&@ zrB~0E+38b6sfezlmhDej*KRVbzptE0Xg%$xpjqoeL;-LwmKIR#%+EZ7U|&;9rS6lo8u9iOD;-3HF{Gm=EL@W zG8L9&8=FxGHICO+MX@lC?DpY4GAE9!S+7hKsTmr8%hFI9QGI4sCj&?Of-yA98KvLsP z|k5cP?Z zay4&3t8e5RgA_@c7z{RX6d`;{B~l03#AD@RJD1{;4x93d7mD15wnFLi^LI%`Z~6@ zq9}|AG1Lq-1~Fb{1b?}bFLaSnWm!7L)P8#%g{{}}u@Q`4N{s3LiD4kSqTnM8UNN4XQi57LZRzkkL9+rJ{_?juO;cZL=MIT2H1q-=Tt1G666hVaPojp^(AM>6 zDQQf0_>1u=rvT+6(5 zAQR5%mlLdhkl4MpIyY0GN9VrGYkq?1sF8F(VeB0u3{p`h6IgEBC}Jr!^-)@5@<8s( zXyiL`ENayjlbGx}3q2T;y&|@~&$+T=hN0iS4BAARQ_JBclEeBW7}$3lx|!Ee&vs&o z=A4b##+t=rylLD-dc(X)^d?KbmU^9uZ)zXbIPC%pD{s(>p9*fu8&(?$LE67%%b-e) z!IU|lpUpK`<&YPqJnj5wb8(;a)JoC~+Kb`Fq-HL<>X@DYPqu4t9tLfS9C>Kn*Ho zl3Zz2y8;bCi@KYchQ;1JTPXL`ZMCb4R7fLlP_qKJ`aTs3H2Q6`g3GdtURX%yk`~xS z#|RDc0Y|%b+$^QYCSEG~ZF;*rT;@T=Ko6uwRJ&RasW^4$W<^nS^v|}UmIHe`P{(x| zI&y@A&b6=G2#r*st8^|19`Yw20=}MF9@@6zIuB%!vd7J%E|@zK(MRvFif-szGX^db zIvb}^{t9g(lZhLP&h6;2p>69mWE3ss6di_-KeYjPVskOMEu?5m_A>;o`6 z5ot9G8pI8Jwi@yJExKVZVw-3FD7TW3Ya{_*rS5+LicF^BX(Mq)H&l_B5o9^ zpcL6s^X}J-_9RAs(wk7s1J$cjO~jo*4l3!1V)$J+_j7t8g4A=ab`L(-{#G?z>z@KneXt&ZOv>m);*lTA}gRhYxtJt;0QZ<#l+OWu6(%(tdZ`LkXb}TQjhal;1vd{D+b@g7G z25i;qgu#ieYC?Fa?iwzeLiJa|vAU1AggN5q{?O?J9YU|xHi}PZb<6>I7->aWA4Y7-|a+7)RQagGQn@cj+ED7h6!b>XIIVI=iT(