diff --git a/MIGRATIONS.md b/MIGRATIONS.md index 4bafa163c966..8de9f8ed4c02 100644 --- a/MIGRATIONS.md +++ b/MIGRATIONS.md @@ -3,6 +3,32 @@ This file documents changes in the data model. Please explain any changes to the data model as well as any custom migrations. +## WordPress 120 + +@chipsnyder 2021-04-12 + +- Created a new entity `BlockEditorSettings` with: + - `isFSETheme` (required, default `false`, `Boolean`) FSE = "Full Site Editing" + - `lastUpdated` (required, no default, `Date`) + +- Created a new entity `BlockEditorSettingElement` with: + - `type` (required, no default, `String`) + - `value` (required, no default, `String`) + - `slug` (required, no default, `String`) + - `name` ( required, no default, `String`) + +- Created one-to-many relationship between `BlockEditorSettings` and `BlockEditorSettingElement` + - `BlockEditorSettings` + - `elements` (optional, to-many, cascade on delete) + - `BlockEditorSettingElement` + - `settings` (required, to-one, nullify on delete) + +- Created one-to-one relationship between `Blog` and `BlockEditorSettings` + - `BlockEditorSettings` + - `blockEditorSettings` (optional, to-one, cascade on delete) + - `BlockEditorSettings` + - `blog` (required, to-one, nullify on delete) + ## WordPress 119 @mkevins 2021-03-31 diff --git a/Podfile b/Podfile index 0e32bf192eb5..8b0efe5415af 100644 --- a/Podfile +++ b/Podfile @@ -47,7 +47,7 @@ def wordpress_ui end def wordpress_kit - pod 'WordPressKit', '~> 4.30.0' + pod 'WordPressKit', '~> 4.31.0-beta3' # pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :tag => '' # pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :branch => '' # pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :commit => '' @@ -161,7 +161,7 @@ abstract_target 'Apps' do ## Gutenberg (React Native) ## ===================== ## - gutenberg :tag => 'v1.50.0' + gutenberg :tag => 'v1.51.0-alpha1' ## Third party libraries diff --git a/Podfile.lock b/Podfile.lock index d8edd1197593..4a26af0b781c 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -401,7 +401,7 @@ PODS: - WordPressKit (~> 4.18-beta) - WordPressShared (~> 1.12-beta) - WordPressUI (~> 1.7-beta) - - WordPressKit (4.30.0): + - WordPressKit (4.31.0-beta.3): - Alamofire (~> 4.8.0) - CocoaLumberjack (~> 3.4) - NSObject-SafeExpectations (= 0.0.4) @@ -443,14 +443,14 @@ DEPENDENCIES: - CocoaLumberjack (~> 3.0) - CropViewController (= 2.5.3) - Down (~> 0.6.6) - - FBLazyVector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/FBLazyVector.podspec.json`) - - FBReactNativeSpec (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/FBReactNativeSpec.podspec.json`) - - Folly (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/Folly.podspec.json`) + - FBLazyVector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/FBLazyVector.podspec.json`) + - FBReactNativeSpec (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/FBReactNativeSpec.podspec.json`) + - Folly (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/Folly.podspec.json`) - FSInteractiveMap (from `https://github.com/wordpress-mobile/FSInteractiveMap.git`, tag `0.2.0`) - Gifu (= 3.2.0) - - glog (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/glog.podspec.json`) + - glog (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/glog.podspec.json`) - Gridicons (~> 1.1.0) - - Gutenberg (from `https://github.com/wordpress-mobile/gutenberg-mobile.git`, tag `v1.50.0`) + - Gutenberg (from `https://github.com/wordpress-mobile/gutenberg-mobile.git`, tag `v1.51.0-alpha1`) - JTAppleCalendar (~> 8.0.2) - Kanvas (~> 1.2.6) - MediaEditor (~> 1.2.1) @@ -460,58 +460,57 @@ DEPENDENCIES: - "NSURL+IDN (~> 0.4)" - OCMock (= 3.4.3) - OHHTTPStubs/Swift (~> 9.1.0) - - RCTRequired (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/RCTRequired.podspec.json`) - - RCTTypeSafety (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/RCTTypeSafety.podspec.json`) + - RCTRequired (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/RCTRequired.podspec.json`) + - RCTTypeSafety (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/RCTTypeSafety.podspec.json`) - Reachability (= 3.2) - - React (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React.podspec.json`) - - React-Core (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-Core.podspec.json`) - - React-CoreModules (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-CoreModules.podspec.json`) - - React-cxxreact (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-cxxreact.podspec.json`) - - React-jsi (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-jsi.podspec.json`) - - React-jsiexecutor (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-jsiexecutor.podspec.json`) - - React-jsinspector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-jsinspector.podspec.json`) - - react-native-blur (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/react-native-blur.podspec.json`) - - react-native-get-random-values (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/react-native-get-random-values.podspec.json`) - - react-native-keyboard-aware-scroll-view (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/react-native-keyboard-aware-scroll-view.podspec.json`) - - react-native-linear-gradient (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/react-native-linear-gradient.podspec.json`) - - react-native-safe-area (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/react-native-safe-area.podspec.json`) - - react-native-safe-area-context (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/react-native-safe-area-context.podspec.json`) - - react-native-slider (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/react-native-slider.podspec.json`) - - react-native-video (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/react-native-video.podspec.json`) - - React-RCTActionSheet (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-RCTActionSheet.podspec.json`) - - React-RCTAnimation (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-RCTAnimation.podspec.json`) - - React-RCTBlob (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-RCTBlob.podspec.json`) - - React-RCTImage (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-RCTImage.podspec.json`) - - React-RCTLinking (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-RCTLinking.podspec.json`) - - React-RCTNetwork (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-RCTNetwork.podspec.json`) - - React-RCTSettings (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-RCTSettings.podspec.json`) - - React-RCTText (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-RCTText.podspec.json`) - - React-RCTVibration (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-RCTVibration.podspec.json`) - - ReactCommon (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/ReactCommon.podspec.json`) - - ReactNativeDarkMode (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/ReactNativeDarkMode.podspec.json`) - - RNCMaskedView (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/RNCMaskedView.podspec.json`) - - RNGestureHandler (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/RNGestureHandler.podspec.json`) - - RNReanimated (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/RNReanimated.podspec.json`) - - RNScreens (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/RNScreens.podspec.json`) - - RNSVG (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/RNSVG.podspec.json`) - - RNTAztecView (from `https://github.com/wordpress-mobile/gutenberg-mobile.git`, tag `v1.50.0`) + - React (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React.podspec.json`) + - React-Core (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-Core.podspec.json`) + - React-CoreModules (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-CoreModules.podspec.json`) + - React-cxxreact (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-cxxreact.podspec.json`) + - React-jsi (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-jsi.podspec.json`) + - React-jsiexecutor (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-jsiexecutor.podspec.json`) + - React-jsinspector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-jsinspector.podspec.json`) + - react-native-blur (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/react-native-blur.podspec.json`) + - react-native-get-random-values (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/react-native-get-random-values.podspec.json`) + - react-native-keyboard-aware-scroll-view (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/react-native-keyboard-aware-scroll-view.podspec.json`) + - react-native-linear-gradient (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/react-native-linear-gradient.podspec.json`) + - react-native-safe-area (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/react-native-safe-area.podspec.json`) + - react-native-safe-area-context (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/react-native-safe-area-context.podspec.json`) + - react-native-slider (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/react-native-slider.podspec.json`) + - react-native-video (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/react-native-video.podspec.json`) + - React-RCTActionSheet (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-RCTActionSheet.podspec.json`) + - React-RCTAnimation (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-RCTAnimation.podspec.json`) + - React-RCTBlob (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-RCTBlob.podspec.json`) + - React-RCTImage (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-RCTImage.podspec.json`) + - React-RCTLinking (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-RCTLinking.podspec.json`) + - React-RCTNetwork (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-RCTNetwork.podspec.json`) + - React-RCTSettings (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-RCTSettings.podspec.json`) + - React-RCTText (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-RCTText.podspec.json`) + - React-RCTVibration (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-RCTVibration.podspec.json`) + - ReactCommon (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/ReactCommon.podspec.json`) + - ReactNativeDarkMode (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/ReactNativeDarkMode.podspec.json`) + - RNCMaskedView (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/RNCMaskedView.podspec.json`) + - RNGestureHandler (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/RNGestureHandler.podspec.json`) + - RNReanimated (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/RNReanimated.podspec.json`) + - RNScreens (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/RNScreens.podspec.json`) + - RNSVG (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/RNSVG.podspec.json`) + - RNTAztecView (from `https://github.com/wordpress-mobile/gutenberg-mobile.git`, tag `v1.51.0-alpha1`) - Starscream (= 3.0.6) - SVProgressHUD (= 2.2.5) - WordPress-Editor-iOS (~> 1.19.4) - WordPressAuthenticator (~> 1.36.0) - - WordPressKit (~> 4.30.0) + - WordPressKit (~> 4.31.0-beta3) - WordPressMocks (~> 0.0.9) - WordPressShared (~> 1.16.0) - WordPressUI (~> 1.9.0) - WPMediaPicker (~> 1.7.2) - - Yoga (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/Yoga.podspec.json`) + - Yoga (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/Yoga.podspec.json`) - ZendeskSupportSDK (= 5.2.0) - ZIPFoundation (~> 0.9.8) SPEC REPOS: https://github.com/wordpress-mobile/cocoapods-specs.git: - WordPressAuthenticator - - WordPressKit trunk: - 1PasswordExtension - Alamofire @@ -551,6 +550,7 @@ SPEC REPOS: - UIDeviceIdentifier - WordPress-Aztec-iOS - WordPress-Editor-iOS + - WordPressKit - WordPressMocks - WordPressShared - WordPressUI @@ -567,92 +567,92 @@ SPEC REPOS: EXTERNAL SOURCES: FBLazyVector: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/FBLazyVector.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/FBLazyVector.podspec.json FBReactNativeSpec: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/FBReactNativeSpec.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/FBReactNativeSpec.podspec.json Folly: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/Folly.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/Folly.podspec.json FSInteractiveMap: :git: https://github.com/wordpress-mobile/FSInteractiveMap.git :tag: 0.2.0 glog: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/glog.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/glog.podspec.json Gutenberg: :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true - :tag: v1.50.0 + :tag: v1.51.0-alpha1 RCTRequired: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/RCTRequired.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/RCTRequired.podspec.json RCTTypeSafety: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/RCTTypeSafety.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/RCTTypeSafety.podspec.json React: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React.podspec.json React-Core: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-Core.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-Core.podspec.json React-CoreModules: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-CoreModules.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-CoreModules.podspec.json React-cxxreact: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-cxxreact.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-cxxreact.podspec.json React-jsi: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-jsi.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-jsi.podspec.json React-jsiexecutor: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-jsiexecutor.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-jsiexecutor.podspec.json React-jsinspector: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-jsinspector.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-jsinspector.podspec.json react-native-blur: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/react-native-blur.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/react-native-blur.podspec.json react-native-get-random-values: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/react-native-get-random-values.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/react-native-get-random-values.podspec.json react-native-keyboard-aware-scroll-view: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/react-native-keyboard-aware-scroll-view.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/react-native-keyboard-aware-scroll-view.podspec.json react-native-linear-gradient: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/react-native-linear-gradient.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/react-native-linear-gradient.podspec.json react-native-safe-area: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/react-native-safe-area.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/react-native-safe-area.podspec.json react-native-safe-area-context: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/react-native-safe-area-context.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/react-native-safe-area-context.podspec.json react-native-slider: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/react-native-slider.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/react-native-slider.podspec.json react-native-video: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/react-native-video.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/react-native-video.podspec.json React-RCTActionSheet: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-RCTActionSheet.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-RCTActionSheet.podspec.json React-RCTAnimation: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-RCTAnimation.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-RCTAnimation.podspec.json React-RCTBlob: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-RCTBlob.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-RCTBlob.podspec.json React-RCTImage: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-RCTImage.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-RCTImage.podspec.json React-RCTLinking: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-RCTLinking.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-RCTLinking.podspec.json React-RCTNetwork: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-RCTNetwork.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-RCTNetwork.podspec.json React-RCTSettings: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-RCTSettings.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-RCTSettings.podspec.json React-RCTText: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-RCTText.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-RCTText.podspec.json React-RCTVibration: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/React-RCTVibration.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/React-RCTVibration.podspec.json ReactCommon: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/ReactCommon.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/ReactCommon.podspec.json ReactNativeDarkMode: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/ReactNativeDarkMode.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/ReactNativeDarkMode.podspec.json RNCMaskedView: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/RNCMaskedView.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/RNCMaskedView.podspec.json RNGestureHandler: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/RNGestureHandler.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/RNGestureHandler.podspec.json RNReanimated: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/RNReanimated.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/RNReanimated.podspec.json RNScreens: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/RNScreens.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/RNScreens.podspec.json RNSVG: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/RNSVG.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/RNSVG.podspec.json RNTAztecView: :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true - :tag: v1.50.0 + :tag: v1.51.0-alpha1 Yoga: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.50.0/third-party-podspecs/Yoga.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.51.0-alpha1/third-party-podspecs/Yoga.podspec.json CHECKOUT OPTIONS: FSInteractiveMap: @@ -661,11 +661,11 @@ CHECKOUT OPTIONS: Gutenberg: :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true - :tag: v1.50.0 + :tag: v1.51.0-alpha1 RNTAztecView: :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true - :tag: v1.50.0 + :tag: v1.51.0-alpha1 SPEC CHECKSUMS: 1PasswordExtension: f97cc80ae58053c331b2b6dc8843ba7103b33794 @@ -747,7 +747,7 @@ SPEC CHECKSUMS: WordPress-Aztec-iOS: 870c93297849072aadfc2223e284094e73023e82 WordPress-Editor-iOS: 068b32d02870464ff3cb9e3172e74234e13ed88c WordPressAuthenticator: 21d96070b30c4ce6b98de52c05779d27c2f9b399 - WordPressKit: 894ed4ad3af910b3e5a00fbd018724eac26a6133 + WordPressKit: 56f5087518977744c2e7b4e8a39abcc264f937f2 WordPressMocks: 903d2410f41a09fb2e0a1b44ad36ad80310570fb WordPressShared: 0f7f10e96f8354d64f951c223ae61e8de7495a46 WordPressUI: 3b70cccc4c44cff09024ca3e94ae25a8768b26c1 @@ -763,6 +763,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: e100a7a0a1bb5d7d43abbde3338727d985a4986d ZIPFoundation: e27423c004a5a1410c15933407747374e7c6cb6e -PODFILE CHECKSUM: 483c37b687db38acd25eb3ee225978b7a15cd573 +PODFILE CHECKSUM: 1b1d7b8bb3db941a8108218ca0aed7c9b93d4826 COCOAPODS: 1.10.0 diff --git a/WordPress/Classes/Models/BlockEditorSettingElement+CoreDataClass.swift b/WordPress/Classes/Models/BlockEditorSettingElement+CoreDataClass.swift new file mode 100644 index 000000000000..0283d26c4ba4 --- /dev/null +++ b/WordPress/Classes/Models/BlockEditorSettingElement+CoreDataClass.swift @@ -0,0 +1,7 @@ +import Foundation +import CoreData + +@objc(BlockEditorSettingElement) +public class BlockEditorSettingElement: NSManagedObject { + +} diff --git a/WordPress/Classes/Models/BlockEditorSettingElement+CoreDataProperties.swift b/WordPress/Classes/Models/BlockEditorSettingElement+CoreDataProperties.swift new file mode 100644 index 000000000000..d25a6ff80330 --- /dev/null +++ b/WordPress/Classes/Models/BlockEditorSettingElement+CoreDataProperties.swift @@ -0,0 +1,59 @@ +import Foundation +import CoreData + +enum BlockEditorSettingElementTypes: String { + case color + case gradient + + var valueKey: String { + self.rawValue + } +} + +extension BlockEditorSettingElement { + + @nonobjc public class func fetchRequest() -> NSFetchRequest<BlockEditorSettingElement> { + return NSFetchRequest<BlockEditorSettingElement>(entityName: "BlockEditorSettingElement") + } + + /// Stores the associated type that this object represents. + /// Available types are defined in `BlockEditorSettingElementTypes` + /// + @NSManaged public var type: String + + /// Stores the value for the associated type. The associated field in the API response might differ based on the type. + /// + @NSManaged public var value: String + + /// Stores a unique key associated to the `value`. + /// + @NSManaged public var slug: String + + /// Stores a user friendly display name for the `slug`. + /// + @NSManaged public var name: String + + /// Stores a reference back to the parent `BlockEditorSettings`. + /// + @NSManaged public var settings: BlockEditorSettings +} + +extension BlockEditorSettingElement: Identifiable { + var rawRepresentation: [String: String]? { + guard let type = BlockEditorSettingElementTypes(rawValue: self.type) else { return nil } + return [ + #keyPath(BlockEditorSettingElement.slug): self.slug, + #keyPath(BlockEditorSettingElement.name): self.name, + type.valueKey: self.value + ] + } + + convenience init(fromRawRepresentation rawObject: [String: String], type: BlockEditorSettingElementTypes, context: NSManagedObjectContext) { + self.init(context: context) + + self.type = type.rawValue + self.value = rawObject[type.valueKey] ?? "" + self.slug = rawObject[#keyPath(BlockEditorSettingElement.slug)] ?? "" + self.name = rawObject[ #keyPath(BlockEditorSettingElement.name)] ?? "" + } +} diff --git a/WordPress/Classes/Models/BlockEditorSettings+CoreDataClass.swift b/WordPress/Classes/Models/BlockEditorSettings+CoreDataClass.swift new file mode 100644 index 000000000000..43e2d73ab70e --- /dev/null +++ b/WordPress/Classes/Models/BlockEditorSettings+CoreDataClass.swift @@ -0,0 +1,7 @@ +import Foundation +import CoreData + +@objc(BlockEditorSettings) +public class BlockEditorSettings: NSManagedObject { + +} diff --git a/WordPress/Classes/Models/BlockEditorSettings+CoreDataProperties.swift b/WordPress/Classes/Models/BlockEditorSettings+CoreDataProperties.swift new file mode 100644 index 000000000000..eabdf0c8532b --- /dev/null +++ b/WordPress/Classes/Models/BlockEditorSettings+CoreDataProperties.swift @@ -0,0 +1,51 @@ +import Foundation +import CoreData + +extension BlockEditorSettings { + + @nonobjc public class func fetchRequest() -> NSFetchRequest<BlockEditorSettings> { + return NSFetchRequest<BlockEditorSettings>(entityName: "BlockEditorSettings") + } + + /// Stores a n MD5 checksum representing the stored data. Used for a comparison to decide if the data has changed. + /// + @NSManaged public var checksum: String + + /// Stores a Bool indicating if the theme supports Full Site Editing (FSE) or not. `true` means the theme is an FSE theme. + /// Default is `false` + /// + @NSManaged public var isFSETheme: Bool + + /// Stores a date indicating the last time stamp that the settings were modified. + /// + @NSManaged public var lastUpdated: Date + + /// Stores a set of attributes describing values that are represented with arrays in the API request. + /// Available types are defined in `BlockEditorSettingElementTypes` + /// + @NSManaged public var elements: Set<BlockEditorSettingElement>? + + /// Stores a reference back to the parent blog. + /// + @NSManaged public var blog: Blog +} + +// MARK: Generated accessors for elements +extension BlockEditorSettings { + + @objc(addElementsObject:) + @NSManaged public func addToElements(_ value: BlockEditorSettingElement) + + @objc(removeElementsObject:) + @NSManaged public func removeFromElements(_ value: BlockEditorSettingElement) + + @objc(addElements:) + @NSManaged public func addToElements(_ values: Set<BlockEditorSettingElement>) + + @objc(removeElements:) + @NSManaged public func removeFromElements(_ values: Set<BlockEditorSettingElement>) +} + +extension BlockEditorSettings: Identifiable { + +} diff --git a/WordPress/Classes/Models/BlockEditorSettings+GutenbergEditorTheme.swift b/WordPress/Classes/Models/BlockEditorSettings+GutenbergEditorTheme.swift new file mode 100644 index 000000000000..8acd091e6c94 --- /dev/null +++ b/WordPress/Classes/Models/BlockEditorSettings+GutenbergEditorTheme.swift @@ -0,0 +1,40 @@ +import Foundation +import Gutenberg + +extension BlockEditorSettings: GutenbergEditorTheme { + public var colors: [[String: String]]? { + elementsByType(.color) + } + + public var gradients: [[String: String]]? { + elementsByType(.gradient) + } + + private func elementsByType(_ type: BlockEditorSettingElementTypes) -> [[String: String]]? { + return elements?.compactMap({ (element) -> [String: String]? in + guard element.type == type.rawValue else { return nil } + return element.rawRepresentation + }) + } +} + +extension BlockEditorSettings { + convenience init?(editorTheme: EditorTheme, context: NSManagedObjectContext) { + self.init(context: context) + self.lastUpdated = Date() + self.checksum = editorTheme.checksum + + var parsedElements = Set<BlockEditorSettingElement>() + if let themeSupport = editorTheme.themeSupport { + themeSupport.colors?.forEach({ (color) in + parsedElements.insert(BlockEditorSettingElement(fromRawRepresentation: color, type: .color, context: context)) + }) + + themeSupport.gradients?.forEach({ (gradient) in + parsedElements.insert(BlockEditorSettingElement(fromRawRepresentation: gradient, type: .gradient, context: context)) + }) + } + + self.elements = parsedElements + } +} diff --git a/WordPress/Classes/Models/Blog+BlockEditorSettings.swift b/WordPress/Classes/Models/Blog+BlockEditorSettings.swift new file mode 100644 index 000000000000..61e0f805fde5 --- /dev/null +++ b/WordPress/Classes/Models/Blog+BlockEditorSettings.swift @@ -0,0 +1,10 @@ +import Foundation +import CoreData + +extension Blog { + + /// Stores the relationship to the `BlockEditorSettings` which is an optional entity that holds settings realated to the BlockEditor. These are features + /// such as Global Styles and Full Site Editing settings and capabilities. + /// + @NSManaged public var blockEditorSettings: BlockEditorSettings? +} diff --git a/WordPress/Classes/Models/EditorTheme.swift b/WordPress/Classes/Models/EditorTheme.swift index f2761cb89f47..e5ff9fde022b 100644 --- a/WordPress/Classes/Models/EditorTheme.swift +++ b/WordPress/Classes/Models/EditorTheme.swift @@ -1,30 +1,35 @@ import Foundation import Gutenberg -struct EditorTheme: Codable, Equatable { +struct EditorTheme: Codable { static func == (lhs: EditorTheme, rhs: EditorTheme) -> Bool { - return lhs.description == rhs.description + return lhs.checksum == rhs.checksum } enum CodingKeys: String, CodingKey { case themeSupport = "theme_supports" - case version - case stylesheet } let themeSupport: EditorThemeSupport? - let version: String? - let stylesheet: String? - - var description: String { - return "\(stylesheet ?? "")-\(version ?? "")" - } + let checksum: String init(from decoder: Decoder) throws { let map = try decoder.container(keyedBy: CodingKeys.self) - self.themeSupport = try? map.decode(EditorThemeSupport.self, forKey: .themeSupport) - self.version = try? map.decode(String.self, forKey: .version) - self.stylesheet = try? map.decode(String.self, forKey: .stylesheet) + let parsedTheme = try? map.decode(EditorThemeSupport.self, forKey: .themeSupport) + self.themeSupport = parsedTheme + self.checksum = { + guard let parsedTheme = parsedTheme else { return "" } + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + let result: String + do { + let data = try encoder.encode(parsedTheme) + result = String(data: data, encoding: .utf8) ?? "" + } catch { + result = "" + } + return result.md5() + }() } } diff --git a/WordPress/Classes/Services/BlockEditorSettingsService.swift b/WordPress/Classes/Services/BlockEditorSettingsService.swift new file mode 100644 index 000000000000..1cecc1460a4a --- /dev/null +++ b/WordPress/Classes/Services/BlockEditorSettingsService.swift @@ -0,0 +1,140 @@ +import Foundation +import WordPressKit + +class BlockEditorSettingsService { + typealias BlockEditorSettingsServiceCompletion = (_ hasChanges: Bool, _ blockEditorSettings: BlockEditorSettings?) -> Void + + let blog: Blog + let remoteAPI: WordPressRestApi + let context: NSManagedObjectContext + + var cachedSettings: BlockEditorSettings? { + return blog.blockEditorSettings + } + + convenience init?(blog: Blog, context: NSManagedObjectContext) { + let remoteAPI: WordPressRestApi + if blog.isAccessibleThroughWPCom(), + blog.dotComID?.intValue != nil, + let restAPI = blog.wordPressComRestApi() { + remoteAPI = restAPI + } else if let orgAPI = blog.wordPressOrgRestApi { + remoteAPI = orgAPI + } else { + // This is should only happen if there is a problem with the blog itsself. + return nil + } + + self.init(blog: blog, remoteAPI: remoteAPI, context: context) + } + + init(blog: Blog, remoteAPI: WordPressRestApi, context: NSManagedObjectContext) { + self.blog = blog + self.remoteAPI = remoteAPI + self.context = context + } + + func fetchSettings(_ completion: @escaping BlockEditorSettingsServiceCompletion) { + fetchTheme(completion) + } +} + +// MARK: Editor `theme_supports` support +private extension BlockEditorSettingsService { + func fetchTheme(_ completion: @escaping BlockEditorSettingsServiceCompletion) { + let requestPath = "/wp/v2/themes?status=active" + let modifiedPath = remoteAPI.requestPath(fromOrgPath: requestPath, with: blog.dotComID?.intValue) + remoteAPI.GET(modifiedPath, parameters: nil) { [weak self] (result, _) in + guard let `self` = self else { return } + switch result { + case .success(let response): + self.processResponse(response) { editorTheme in + self.context.perform { + let originalChecksum = self.blog.blockEditorSettings?.checksum ?? "" + self.updateCache(originalChecksum: originalChecksum, editorTheme: editorTheme, completion: completion) + } + } + case .failure(let error): + DDLogError("Error loading active theme: \(error)") + } + } + } + + func processResponse(_ response: Any, completion: (_ editorTheme: EditorTheme?) -> Void) { + guard let responseData = try? JSONSerialization.data(withJSONObject: response, options: []), + let editorThemes = try? JSONDecoder().decode([EditorTheme].self, from: responseData) else { + completion(nil) + return + } + completion(editorThemes.first) + } + + func updateCache(originalChecksum: String, editorTheme: EditorTheme?, completion: @escaping BlockEditorSettingsServiceCompletion) { + let newChecksum = editorTheme?.checksum ?? "" + guard originalChecksum != newChecksum else { + /// The fetched Editor Theme is the same as the cached one so respond with no new changes. + completion(false, self.blog.blockEditorSettings) + return + } + + guard let editorTheme = editorTheme else { + /// The original checksum is different than an empty one so we need to claer the old settings. + clearCoreData(completion: completion) + return + } + + /// The fetched Editor Theme is different than the cached one so persist the new one and delete the old one. + context.perform { + self.persistToCoreData(blogID: self.blog.objectID, editorTheme: editorTheme) { success in + guard success else { + completion(false, nil) + return + } + + self.context.perform { + completion(true, self.blog.blockEditorSettings) + } + } + } + } + + func persistToCoreData(blogID: NSManagedObjectID, editorTheme: EditorTheme, completion: @escaping (_ success: Bool) -> Void) { + let parsingContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) + parsingContext.parent = context + parsingContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump + + parsingContext.perform { + guard let blog = parsingContext.object(with: blogID) as? Blog else { + completion(false) + return + } + + if let blockEditorSettings = blog.blockEditorSettings { + // Block Editor Settings nullify on delete + parsingContext.delete(blockEditorSettings) + } + + blog.blockEditorSettings = BlockEditorSettings(editorTheme: editorTheme, context: parsingContext) + try? parsingContext.save() + completion(true) + } + } +} + +// MARK: Editor Global Styles support +private extension BlockEditorSettingsService { +// ToDo: Add support for Global Styles https://github.com/wordpress-mobile/gutenberg-mobile/issues/3163 +} + +// MARK: Shared Core Data Support +private extension BlockEditorSettingsService { + func clearCoreData(completion: @escaping BlockEditorSettingsServiceCompletion) { + self.context.perform { + if let blockEditorSettings = self.blog.blockEditorSettings { + // Block Editor Settings nullify on delete + self.context.delete(blockEditorSettings) + } + completion(true, nil) + } + } +} diff --git a/WordPress/Classes/Stores/EditorThemeStore.swift b/WordPress/Classes/Stores/EditorThemeStore.swift deleted file mode 100644 index 0ea01b9f422b..000000000000 --- a/WordPress/Classes/Stores/EditorThemeStore.swift +++ /dev/null @@ -1,126 +0,0 @@ -import WordPressFlux - -struct EditorThemeQuery { - let blog: Blog -} - -enum EditorThemeStoreState { - typealias StoredThemes = [String: EditorTheme] - case empty - case loaded(StoredThemes) - - static func key(forBlog blog: Blog) -> String? { - return blog.hostname as String? - } - - func editorTheme(forBlog blog: Blog) -> EditorTheme? { - guard let themeKey = EditorThemeStoreState.key(forBlog: blog) else { - return nil - } - - switch self { - case .loaded(let themes): - return themes[themeKey] - default: - return nil - } - } - - func storedThemes() -> StoredThemes { - switch self { - case .loaded(let themes): - return themes - default: - return [:] - } - } -} - -extension EditorThemeStoreState: Codable { - - enum Key: CodingKey { - case rawValue - case associatedValue - } - - enum CodingError: Error { - case unknownValue - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Key.self) - let rawValue = try container.decode(Int.self, forKey: .rawValue) - switch rawValue { - case 0: - self = .empty - case 1: - let themes = try container.decode(StoredThemes.self, forKey: .associatedValue) - self = .loaded(themes) - default: - throw CodingError.unknownValue - } - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: Key.self) - switch self { - case .empty: - try container.encode(0, forKey: .rawValue) - case .loaded(let themes): - try container.encode(1, forKey: .rawValue) - try container.encode(themes, forKey: .associatedValue) - } - } -} - -class EditorThemeStore: QueryStore<EditorThemeStoreState, EditorThemeQuery> { - - private enum ErrorCode: Int { - case processingError - } - - init(dispatcher: ActionDispatcher = .global) { - super.init(initialState: .empty, dispatcher: dispatcher) - } - - override func queriesChanged() { - - activeQueries.forEach { (query) in - fetchTheme(for: query.blog) - } - } - - override func logError(_ error: String) { - DDLogError("Error loading active theme: \(error)") - } -} - -private extension EditorThemeStore { - - func fetchTheme(for blog: Blog) { - let requestPath = "/wp/v2/themes?status=active" - GutenbergNetworkRequest(path: requestPath, blog: blog).request { [weak self] result in - switch result { - case .success(let response): - self?.processResponse(response, for: blog) - case .failure(let error): - DDLogError("Error loading active theme: \(error)") - } - } - } - - func processResponse(_ response: Any, for blog: Blog) { - guard - let responseData = try? JSONSerialization.data(withJSONObject: response, options: []), - let themeKey = EditorThemeStoreState.key(forBlog: blog), - let themeSupports = try? JSONDecoder().decode([EditorTheme].self, from: responseData), - let newTheme = themeSupports.first - else { return } - - var existingThemes = state.storedThemes() - if newTheme != existingThemes[themeKey] { - existingThemes[themeKey] = newTheme - state = .loaded(existingThemes) - } - } -} diff --git a/WordPress/Classes/Stores/StoreContainer.swift b/WordPress/Classes/Stores/StoreContainer.swift index 00088db944be..3e0e572d34c6 100644 --- a/WordPress/Classes/Stores/StoreContainer.swift +++ b/WordPress/Classes/Stores/StoreContainer.swift @@ -20,6 +20,5 @@ class StoreContainer { let statsInsights = StatsInsightsStore() let statsPeriod = StatsPeriodStore() let jetpackInstall = JetpackInstallStore() - let editorTheme = EditorThemeStore() let statsWidgets = StatsWidgetsStore() } diff --git a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift index 278ddd979028..a3e5de3c4aae 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift @@ -285,10 +285,13 @@ class GutenbergViewController: UIViewController, PostEditor { return gutenbergSettings.shouldPresentInformativeDialog(for: post.blog) }() - private var themeSupportQuery: Receipt? = nil - private var themeSupportReceipt: Receipt? = nil - internal private(set) var contentInfo: ContentInfo? + lazy var editorSettingsService: BlockEditorSettingsService? = { + let blog = post.blog + guard let context = blog.managedObjectContext else { return nil } + + return BlockEditorSettingsService(blog: blog, context: context) + }() // MARK: - Initializers required init( @@ -335,7 +338,7 @@ class GutenbergViewController: UIViewController, PostEditor { refreshInterface() gutenberg.delegate = self - fetchEditorTheme() + fetchBlockSettings() presentNewPageNoticeIfNeeded() service?.syncJetpackSettingsForBlog(post.blog, success: { [weak self] in @@ -822,7 +825,6 @@ extension GutenbergViewController: GutenbergBridgeDelegate { } focusTitleIfNeeded() mediaInserterHelper.refreshMediaStatus() - refreshEditorTheme() } } @@ -1187,29 +1189,18 @@ private extension GutenbergViewController { } } -// Editor Theme Support +// Block Editor Settings extension GutenbergViewController { // GutenbergBridgeDataSource func gutenbergEditorTheme() -> GutenbergEditorTheme? { - return StoreContainer.shared.editorTheme.state.editorTheme(forBlog: post.blog)?.themeSupport + return editorSettingsService?.cachedSettings } - private func fetchEditorTheme() { - let themeSupportStore = StoreContainer.shared.editorTheme - themeSupportQuery = themeSupportStore.query(EditorThemeQuery(blog: post.blog)) - themeSupportReceipt = themeSupportStore.onStateChange { [weak self] (_, state) in - DispatchQueue.main.async { - if let strongSelf = self, let themeSupport = state.editorTheme(forBlog: strongSelf.post.blog)?.themeSupport { - strongSelf.gutenberg.updateTheme(themeSupport) - } - } - } - } - - private func refreshEditorTheme() { - if let themeSupport = StoreContainer.shared.editorTheme.state.editorTheme(forBlog: post.blog)?.themeSupport { - gutenberg.updateTheme(themeSupport) - } + private func fetchBlockSettings() { + editorSettingsService?.fetchSettings({ [weak self] (hasChanges, settings) in + guard hasChanges, let `self` = self else { return } + self.gutenberg.updateTheme(settings) + }) } } diff --git a/WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion b/WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion index f30b81605215..ed0ae9c51753 100644 --- a/WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion +++ b/WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ <plist version="1.0"> <dict> <key>_XCCurrentVersionName</key> - <string>WordPress 119.xcdatamodel</string> + <string>WordPress 120.xcdatamodel</string> </dict> </plist> diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 120.xcdatamodel/contents b/WordPress/Classes/WordPress.xcdatamodeld/WordPress 120.xcdatamodel/contents new file mode 100644 index 000000000000..da2c5ffdafc9 --- /dev/null +++ b/WordPress/Classes/WordPress.xcdatamodeld/WordPress 120.xcdatamodel/contents @@ -0,0 +1,1022 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20D91" minimumToolsVersion="Xcode 9.0" sourceLanguage="Swift" userDefinedModelVersionIdentifier=""> + <entity name="AbstractPost" representedClassName="AbstractPost" isAbstract="YES" parentEntity="BasePost"> + <attribute name="autosaveContent" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="autosaveExcerpt" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="autosaveIdentifier" optional="YES" attributeType="Integer 64" usesScalarValueType="NO" syncable="YES"/> + <attribute name="autosaveModifiedDate" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="autosaveTitle" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="autoUploadAttemptsCount" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="confirmedChangesHash" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="confirmedChangesTimestamp" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="dateModified" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="metaIsLocal" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="metaPublishImmediately" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="NO" syncable="YES"/> + <attribute name="revisions" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData" syncable="YES"/> + <attribute name="statusAfterSync" optional="YES" attributeType="String" syncable="YES"/> + <relationship name="blog" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="posts" inverseEntity="Blog" syncable="YES"/> + <relationship name="featuredImage" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Media" inverseName="featuredOnPosts" inverseEntity="Media" syncable="YES"/> + <relationship name="media" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Media" inverseName="posts" inverseEntity="Media" syncable="YES"/> + <relationship name="original" optional="YES" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="AbstractPost" inverseName="revision" inverseEntity="AbstractPost" syncable="YES"/> + <relationship name="revision" optional="YES" minCount="1" maxCount="1" deletionRule="Cascade" destinationEntity="AbstractPost" inverseName="original" inverseEntity="AbstractPost" syncable="YES"/> + <fetchIndex name="byDateModifiedIndex"> + <fetchIndexElement property="dateModified" type="Binary" order="ascending"/> + </fetchIndex> + <fetchIndex name="byBlogIndex"> + <fetchIndexElement property="blog" type="Binary" order="ascending"/> + </fetchIndex> + <fetchIndex name="byMediaIndex"> + <fetchIndexElement property="media" type="Binary" order="ascending"/> + </fetchIndex> + <fetchIndex name="byOriginalIndex"> + <fetchIndexElement property="original" type="Binary" order="ascending"/> + </fetchIndex> + <fetchIndex name="byRevisionIndex"> + <fetchIndexElement property="revision" type="Binary" order="ascending"/> + </fetchIndex> + <userInfo/> + </entity> + <entity name="Account" representedClassName="WPAccount" syncable="YES"> + <attribute name="avatarURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="dateCreated" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="displayName" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="email" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="emailVerified" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="primaryBlogID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="userID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="username" attributeType="String" syncable="YES"/> + <attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/> + <relationship name="blogs" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Blog" inverseName="account" inverseEntity="Blog" syncable="YES"/> + <relationship name="defaultBlog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="accountForDefaultBlog" inverseEntity="Blog" syncable="YES"/> + <relationship name="settings" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="AccountSettings" inverseName="account" inverseEntity="AccountSettings" syncable="YES"/> + <fetchIndex name="byBlogsIndex"> + <fetchIndexElement property="blogs" type="Binary" order="ascending"/> + </fetchIndex> + </entity> + <entity name="AccountSettings" representedClassName=".ManagedAccountSettings" syncable="YES"> + <attribute name="aboutMe" attributeType="String" syncable="YES"/> + <attribute name="blockEmailNotifications" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="displayName" attributeType="String" syncable="YES"/> + <attribute name="email" attributeType="String" syncable="YES"/> + <attribute name="emailPendingAddress" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="emailPendingChange" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="firstName" attributeType="String" syncable="YES"/> + <attribute name="language" attributeType="String" syncable="YES"/> + <attribute name="lastName" attributeType="String" syncable="YES"/> + <attribute name="primarySiteID" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="tracksOptOut" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="username" attributeType="String" syncable="YES"/> + <attribute name="usernameCanBeChanged" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="webAddress" attributeType="String" syncable="YES"/> + <relationship name="account" maxCount="1" deletionRule="Nullify" destinationEntity="Account" inverseName="settings" inverseEntity="Account" syncable="YES"/> + </entity> + <entity name="AllTimeStatsRecordValue" representedClassName=".AllTimeStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="bestViewsDay" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="bestViewsPerDayCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="postsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="viewsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="visitorsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + </entity> + <entity name="AnnualAndMostPopularTimeStatsRecordValue" representedClassName=".AnnualAndMostPopularTimeStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="averageCommentsCount" attributeType="Double" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="averageImagesCount" attributeType="Double" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="averageLikesCount" attributeType="Double" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="averageWordsCount" attributeType="Double" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="insightYear" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="mostPopularDayOfWeek" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="mostPopularDayOfWeekPercentage" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="mostPopularHour" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="mostPopularHourPercentage" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="totalCommentsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="totalImagesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="totalLikesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="totalPostsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="totalWordsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + </entity> + <entity name="BasePost" representedClassName="BasePost" isAbstract="YES"> + <attribute name="author" optional="YES" attributeType="String"/> + <attribute name="authorAvatarURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="authorID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="content" optional="YES" attributeType="String"/> + <attribute name="date_created_gmt" optional="YES" attributeType="Date" usesScalarValueType="NO"/> + <attribute name="mt_excerpt" optional="YES" attributeType="String"/> + <attribute name="password" optional="YES" attributeType="String"/> + <attribute name="pathForDisplayImage" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="permaLink" optional="YES" attributeType="String"/> + <attribute name="postID" optional="YES" attributeType="Integer 64" defaultValueString="-1" usesScalarValueType="NO"/> + <attribute name="postTitle" optional="YES" attributeType="String"/> + <attribute name="remoteStatusNumber" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/> + <attribute name="status" optional="YES" attributeType="String" defaultValueString="publish"/> + <attribute name="suggested_slug" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="wp_slug" optional="YES" attributeType="String"/> + <relationship name="comments" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Comment" inverseName="post" inverseEntity="Comment" syncable="YES"/> + <fetchIndex name="byAuthorIDIndex"> + <fetchIndexElement property="authorID" type="Binary" order="ascending"/> + </fetchIndex> + <userInfo/> + </entity> + <entity name="BlockEditorSettingElement" representedClassName="BlockEditorSettingElement" syncable="YES"> + <attribute name="name" attributeType="String" syncable="YES"/> + <attribute name="slug" attributeType="String" syncable="YES"/> + <attribute name="type" attributeType="String" syncable="YES"/> + <attribute name="value" attributeType="String" syncable="YES"/> + <relationship name="settings" maxCount="1" deletionRule="Nullify" destinationEntity="BlockEditorSettings" inverseName="elements" inverseEntity="BlockEditorSettings" syncable="YES"/> + </entity> + <entity name="BlockEditorSettings" representedClassName="BlockEditorSettings" syncable="YES"> + <attribute name="checksum" attributeType="String" syncable="YES"/> + <attribute name="isFSETheme" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/> + <attribute name="lastUpdated" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <relationship name="blog" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="blockEditorSettings" inverseEntity="Blog" syncable="YES"/> + <relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="BlockEditorSettingElement" inverseName="settings" inverseEntity="BlockEditorSettingElement" syncable="YES"/> + </entity> + <entity name="Blog" representedClassName="Blog"> + <attribute name="apiKey" optional="YES" attributeType="String"/> + <attribute name="blogID" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO"/> + <attribute name="capabilities" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData" syncable="YES"/> + <attribute name="currentThemeId" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="hasDomainCredit" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="hasOlderPages" transient="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="NO"/> + <attribute name="hasOlderPosts" transient="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="NO"/> + <attribute name="hasPaidPlan" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="icon" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="isActivated" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isAdmin" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isHostedAtWPcom" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isMultiAuthor" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="lastCommentsSync" optional="YES" attributeType="Date" usesScalarValueType="NO"/> + <attribute name="lastPagesSync" optional="YES" attributeType="Date" usesScalarValueType="NO"/> + <attribute name="lastPostsSync" optional="YES" attributeType="Date" usesScalarValueType="NO"/> + <attribute name="lastStatsSync" optional="YES" attributeType="Date" usesScalarValueType="NO"/> + <attribute name="lastUpdateWarning" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="mobileEditor" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="options" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData"/> + <attribute name="planID" optional="YES" attributeType="Integer 64" usesScalarValueType="NO" syncable="YES"/> + <attribute name="planTitle" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="postFormats" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData"/> + <attribute name="quotaSpaceAllowed" optional="YES" attributeType="Integer 64" usesScalarValueType="NO" syncable="YES"/> + <attribute name="quotaSpaceUsed" optional="YES" attributeType="Integer 64" usesScalarValueType="NO" syncable="YES"/> + <attribute name="url" attributeType="String"/> + <attribute name="userID" optional="YES" attributeType="Integer 64" usesScalarValueType="NO" syncable="YES"/> + <attribute name="username" optional="YES" attributeType="String"/> + <attribute name="visible" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="NO" syncable="YES"/> + <attribute name="webEditor" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="xmlrpc" attributeType="String"/> + <relationship name="account" optional="YES" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="Account" inverseName="blogs" inverseEntity="Account" syncable="YES"/> + <relationship name="accountForDefaultBlog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Account" inverseName="defaultBlog" inverseEntity="Account" syncable="YES"/> + <relationship name="authors" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="BlogAuthor" inverseName="blog" inverseEntity="BlogAuthor" syncable="YES"/> + <relationship name="blockEditorSettings" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="BlockEditorSettings" inverseName="blog" inverseEntity="BlockEditorSettings" syncable="YES"/> + <relationship name="categories" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Category" inverseName="blog" inverseEntity="Category"/> + <relationship name="comments" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Comment" inverseName="blog" inverseEntity="Comment"/> + <relationship name="connections" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="PublicizeConnection" inverseName="blog" inverseEntity="PublicizeConnection" syncable="YES"/> + <relationship name="domains" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Domain" inverseName="blog" inverseEntity="Domain" syncable="YES"/> + <relationship name="inviteLinks" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="InviteLinks" inverseName="blog" inverseEntity="InviteLinks" syncable="YES"/> + <relationship name="media" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Media" inverseName="blog" inverseEntity="Media"/> + <relationship name="menuLocations" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="MenuLocation" inverseName="blog" inverseEntity="MenuLocation" syncable="YES"/> + <relationship name="menus" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="Menu" inverseName="blog" inverseEntity="Menu" syncable="YES"/> + <relationship name="pageTemplateCategories" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="PageTemplateCategory" inverseName="blog" inverseEntity="PageTemplateCategory" syncable="YES"/> + <relationship name="posts" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="AbstractPost" inverseName="blog" inverseEntity="AbstractPost"/> + <relationship name="postTypes" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="PostType" inverseName="blog" inverseEntity="PostType" syncable="YES"/> + <relationship name="quickStartTours" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="QuickStartTourState" inverseName="blog" inverseEntity="QuickStartTourState" syncable="YES"/> + <relationship name="roles" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Role" inverseName="blog" inverseEntity="Role" syncable="YES"/> + <relationship name="settings" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="BlogSettings" inverseName="blog" inverseEntity="BlogSettings" syncable="YES"/> + <relationship name="sharingButtons" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="SharingButton" inverseName="blog" inverseEntity="SharingButton" syncable="YES"/> + <relationship name="siteSuggestions" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="SiteSuggestion" inverseName="blog" inverseEntity="SiteSuggestion" syncable="YES"/> + <relationship name="statsRecords" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="StatsRecord" inverseName="blog" inverseEntity="StatsRecord" syncable="YES"/> + <relationship name="tags" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="PostTag" inverseName="blog" inverseEntity="PostTag"/> + <relationship name="themes" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Theme" inverseName="blog" inverseEntity="Theme" syncable="YES"/> + <relationship name="userSuggestions" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="UserSuggestion" inverseName="blog" inverseEntity="UserSuggestion" syncable="YES"/> + <fetchIndex name="byAccountIndex"> + <fetchIndexElement property="account" type="Binary" order="ascending"/> + </fetchIndex> + <fetchIndex name="byCategoriesIndex"> + <fetchIndexElement property="categories" type="Binary" order="ascending"/> + </fetchIndex> + <fetchIndex name="byCommentsIndex"> + <fetchIndexElement property="comments" type="Binary" order="ascending"/> + </fetchIndex> + <fetchIndex name="byMediaIndex"> + <fetchIndexElement property="media" type="Binary" order="ascending"/> + </fetchIndex> + <fetchIndex name="byPostsIndex"> + <fetchIndexElement property="posts" type="Binary" order="ascending"/> + </fetchIndex> + <userInfo/> + </entity> + <entity name="BlogAuthor" representedClassName="WordPress.BlogAuthor" syncable="YES"> + <attribute name="avatarURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="displayName" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="email" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="linkedUserID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="primaryBlogID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="userID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="username" optional="YES" attributeType="String" syncable="YES"/> + <relationship name="blog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="authors" inverseEntity="Blog" syncable="YES"/> + </entity> + <entity name="BlogSettings" representedClassName=".BlogSettings" syncable="YES"> + <attribute name="ampEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="ampSupported" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="commentsAllowed" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="commentsBlocklistKeys" optional="YES" attributeType="Transformable" valueTransformerName="SetValueTransformer" elementID="commentsBlacklistKeys" syncable="YES"/> + <attribute name="commentsCloseAutomatically" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="commentsCloseAutomaticallyAfterDays" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="commentsFromKnownUsersAllowlisted" optional="YES" attributeType="Boolean" usesScalarValueType="NO" elementID="commentsFromKnownUsersWhitelisted" syncable="YES"/> + <attribute name="commentsMaximumLinks" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="commentsModerationKeys" optional="YES" attributeType="Transformable" valueTransformerName="SetValueTransformer" syncable="YES"/> + <attribute name="commentsPageSize" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="commentsPagingEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="commentsRequireManualModeration" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="commentsRequireNameAndEmail" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="commentsRequireRegistration" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="commentsSortOrder" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="commentsThreadingDepth" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="commentsThreadingEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="dateFormat" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="defaultCategoryID" optional="YES" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/> + <attribute name="defaultPostFormat" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="geolocationEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/> + <attribute name="gmtOffset" optional="YES" attributeType="Decimal" defaultValueString="0.0" syncable="YES"/> + <attribute name="iconMediaID" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="jetpackBlockMaliciousLoginAttempts" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="jetpackLazyLoadImages" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="jetpackLoginAllowListedIPAddresses" optional="YES" attributeType="Transformable" valueTransformerName="SetValueTransformer" elementID="jetpackLoginWhiteListedIPAddresses" syncable="YES"/> + <attribute name="jetpackMonitorEmailNotifications" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="jetpackMonitorEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="jetpackMonitorPushNotifications" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="jetpackServeImagesFromOurServers" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="jetpackSSOEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="jetpackSSOMatchAccountsByEmail" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="jetpackSSORequireTwoStepAuthentication" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="languageID" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="name" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="pingbackInboundEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="pingbackOutboundEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="postsPerPage" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="privacy" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="relatedPostsAllowed" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="relatedPostsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="relatedPostsShowHeadline" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="relatedPostsShowThumbnails" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="sharingButtonStyle" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="sharingCommentLikesEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="sharingDisabledLikes" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="sharingDisabledReblogs" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="sharingLabel" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="sharingTwitterName" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="startOfWeek" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="tagline" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="timeFormat" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="timezoneString" optional="YES" attributeType="String" syncable="YES"/> + <relationship name="blog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="settings" inverseEntity="Blog" syncable="YES"/> + </entity> + <entity name="Category" representedClassName="PostCategory"> + <attribute name="categoryID" attributeType="Integer 32" defaultValueString="-1" usesScalarValueType="YES"/> + <attribute name="categoryName" attributeType="String"/> + <attribute name="parentID" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/> + <relationship name="blog" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="categories" inverseEntity="Blog"/> + <relationship name="posts" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Post" inverseName="categories" inverseEntity="Post"/> + <fetchIndex name="byBlogIndex"> + <fetchIndexElement property="blog" type="Binary" order="ascending"/> + </fetchIndex> + <fetchIndex name="byPostsIndex"> + <fetchIndexElement property="posts" type="Binary" order="ascending"/> + </fetchIndex> + <userInfo/> + </entity> + <entity name="ClicksStatsRecordValue" representedClassName=".ClicksStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="clicksCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="iconUrlString" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="label" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="urlString" optional="YES" attributeType="String" syncable="YES"/> + <relationship name="children" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ClicksStatsRecordValue" inverseName="parent" inverseEntity="ClicksStatsRecordValue" syncable="YES"/> + <relationship name="parent" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ClicksStatsRecordValue" inverseName="children" inverseEntity="ClicksStatsRecordValue" syncable="YES"/> + </entity> + <entity name="Comment" representedClassName="Comment"> + <attribute name="author" optional="YES" attributeType="String"/> + <attribute name="author_email" optional="YES" attributeType="String"/> + <attribute name="author_ip" optional="YES" attributeType="String"/> + <attribute name="author_url" optional="YES" attributeType="String"/> + <attribute name="authorAvatarURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="commentID" optional="YES" attributeType="Integer 32" usesScalarValueType="NO"/> + <attribute name="content" optional="YES" attributeType="String"/> + <attribute name="dateCreated" optional="YES" attributeType="Date" usesScalarValueType="NO"/> + <attribute name="depth" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="hierarchy" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="isLiked" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="likeCount" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="link" optional="YES" attributeType="String"/> + <attribute name="parentID" optional="YES" attributeType="Integer 32" usesScalarValueType="NO"/> + <attribute name="postID" optional="YES" attributeType="Integer 32" usesScalarValueType="NO"/> + <attribute name="postTitle" optional="YES" attributeType="String"/> + <attribute name="status" optional="YES" attributeType="String"/> + <attribute name="type" optional="YES" attributeType="String"/> + <relationship name="blog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="comments" inverseEntity="Blog" syncable="YES"/> + <relationship name="post" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="BasePost" inverseName="comments" inverseEntity="BasePost" syncable="YES"/> + <fetchIndex name="byStatusIndex"> + <fetchIndexElement property="status" type="Binary" order="ascending"/> + </fetchIndex> + <userInfo/> + </entity> + <entity name="CountryStatsRecordValue" representedClassName=".CountryStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="countryCode" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="countryName" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="viewsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + </entity> + <entity name="DiffAbstractValue" representedClassName="WordPress.DiffAbstractValue" isAbstract="YES" syncable="YES"> + <attribute name="diffOperation" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="diffType" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="index" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="value" optional="YES" attributeType="String" syncable="YES"/> + </entity> + <entity name="DiffContentValue" representedClassName="WordPress.DiffContentValue" parentEntity="DiffAbstractValue" syncable="YES"> + <relationship name="revisionDiff" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RevisionDiff" inverseName="contentDiffs" inverseEntity="RevisionDiff" syncable="YES"/> + </entity> + <entity name="DiffTitleValue" representedClassName="WordPress.DiffTitleValue" parentEntity="DiffAbstractValue" syncable="YES"> + <relationship name="revisionDiff" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RevisionDiff" inverseName="titleDiffs" inverseEntity="RevisionDiff" syncable="YES"/> + </entity> + <entity name="Domain" representedClassName=".ManagedDomain" syncable="YES"> + <attribute name="domainName" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="domainType" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isPrimary" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <relationship name="blog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="domains" inverseEntity="Blog" syncable="YES"/> + </entity> + <entity name="FileDownloadsStatsRecordValue" representedClassName=".FileDownloadsStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="downloadCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="file" optional="YES" attributeType="String" syncable="YES"/> + </entity> + <entity name="FollowersCountStatsRecordValue" representedClassName=".FollowersCountStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="count" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="type" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + </entity> + <entity name="FollowersStatsRecordValue" representedClassName=".FollowersStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="avatarURLString" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="name" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="subscribedDate" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="type" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + </entity> + <entity name="InviteLinks" representedClassName="InviteLinks" syncable="YES"> + <attribute name="expiry" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="groupInvite" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/> + <attribute name="inviteDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="inviteKey" attributeType="String" syncable="YES"/> + <attribute name="isPending" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/> + <attribute name="link" attributeType="String" syncable="YES"/> + <attribute name="role" attributeType="String" syncable="YES"/> + <relationship name="blog" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="inviteLinks" inverseEntity="Blog" syncable="YES"/> + </entity> + <entity name="LastPostStatsRecordValue" representedClassName="WordPress.LastPostStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="commentsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="likesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="postID" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="publishedDate" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="title" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="urlString" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="viewsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + </entity> + <entity name="Media" representedClassName="Media"> + <attribute name="alt" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="autoUploadFailureCount" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="caption" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="creationDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/> + <attribute name="desc" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="error" optional="YES" attributeType="Transformable" valueTransformerName="NSErrorValueTransformer" syncable="YES"/> + <attribute name="filename" optional="YES" attributeType="String"/> + <attribute name="filesize" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO"/> + <attribute name="height" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO"/> + <attribute name="length" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO"/> + <attribute name="localThumbnailIdentifier" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="localThumbnailURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="localURL" optional="YES" attributeType="String"/> + <attribute name="mediaID" optional="YES" attributeType="Integer 32" usesScalarValueType="NO"/> + <attribute name="mediaTypeString" optional="YES" attributeType="String"/> + <attribute name="postID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="remoteStatusNumber" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/> + <attribute name="remoteThumbnailURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="remoteURL" optional="YES" attributeType="String"/> + <attribute name="shortcode" optional="YES" attributeType="String"/> + <attribute name="title" optional="YES" attributeType="String"/> + <attribute name="videopressGUID" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="width" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO"/> + <relationship name="blog" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="media" inverseEntity="Blog"/> + <relationship name="featuredOnPosts" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="AbstractPost" inverseName="featuredImage" inverseEntity="AbstractPost" syncable="YES"/> + <relationship name="posts" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="AbstractPost" inverseName="media" inverseEntity="AbstractPost"/> + <fetchIndex name="byBlogIndex"> + <fetchIndexElement property="blog" type="Binary" order="ascending"/> + </fetchIndex> + <fetchIndex name="byPostsIndex"> + <fetchIndexElement property="posts" type="Binary" order="ascending"/> + </fetchIndex> + <userInfo/> + </entity> + <entity name="Menu" representedClassName="Menu" syncable="YES"> + <attribute name="details" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="menuID" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="name" optional="YES" attributeType="String" syncable="YES"/> + <relationship name="blog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="menus" inverseEntity="Blog" syncable="YES"/> + <relationship name="items" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MenuItem" inverseName="menu" inverseEntity="MenuItem" syncable="YES"/> + <relationship name="locations" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MenuLocation" inverseName="menu" inverseEntity="MenuLocation" syncable="YES"/> + </entity> + <entity name="MenuItem" representedClassName="MenuItem" syncable="YES"> + <attribute name="classes" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData" syncable="YES"/> + <attribute name="contentID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="details" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="itemID" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="linkTarget" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="linkTitle" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="name" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="type" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="typeFamily" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="typeLabel" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="urlStr" optional="YES" attributeType="String" syncable="YES"/> + <relationship name="children" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MenuItem" inverseName="parent" inverseEntity="MenuItem" syncable="YES"/> + <relationship name="menu" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Menu" inverseName="items" inverseEntity="Menu" syncable="YES"/> + <relationship name="parent" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MenuItem" inverseName="children" inverseEntity="MenuItem" syncable="YES"/> + </entity> + <entity name="MenuLocation" representedClassName="MenuLocation" syncable="YES"> + <attribute name="defaultState" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="details" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="name" optional="YES" attributeType="String" syncable="YES"/> + <relationship name="blog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="menuLocations" inverseEntity="Blog" syncable="YES"/> + <relationship name="menu" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Menu" inverseName="locations" inverseEntity="Menu" syncable="YES"/> + </entity> + <entity name="Notification" representedClassName="Notification" syncable="YES"> + <attribute name="body" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData" syncable="YES"/> + <attribute name="header" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData" syncable="YES"/> + <attribute name="icon" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="meta" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData" syncable="YES"/> + <attribute name="noticon" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="notificationHash" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="notificationId" optional="YES" attributeType="String" elementID="simperiumKey" syncable="YES"/> + <attribute name="read" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="subject" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData" syncable="YES"/> + <attribute name="timestamp" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="title" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="type" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="url" optional="YES" attributeType="String" syncable="YES"/> + </entity> + <entity name="OtherAndTotalViewsCountStatsRecordValue" representedClassName=".OtherAndTotalViewsCountStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="otherCount" attributeType="Integer 64" usesScalarValueType="YES" syncable="YES"/> + <attribute name="totalCount" attributeType="Integer 64" usesScalarValueType="YES" syncable="YES"/> + </entity> + <entity name="Page" representedClassName="Page" parentEntity="AbstractPost"> + <attribute name="parentID" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO"/> + <userInfo/> + </entity> + <entity name="PageTemplateCategory" representedClassName="PageTemplateCategory" syncable="YES"> + <attribute name="desc" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="emoji" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="ordinal" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="slug" attributeType="String" syncable="YES"/> + <attribute name="title" attributeType="String" syncable="YES"/> + <relationship name="blog" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="pageTemplateCategories" inverseEntity="Blog" syncable="YES"/> + <relationship name="layouts" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="PageTemplateLayout" inverseName="categories" inverseEntity="PageTemplateLayout" syncable="YES"/> + </entity> + <entity name="PageTemplateLayout" representedClassName="PageTemplateLayout" syncable="YES"> + <attribute name="content" attributeType="String" syncable="YES"/> + <attribute name="demoUrl" attributeType="String" defaultValueString="" syncable="YES"/> + <attribute name="preview" attributeType="String" syncable="YES"/> + <attribute name="previewMobile" attributeType="String" defaultValueString="" syncable="YES"/> + <attribute name="previewTablet" attributeType="String" defaultValueString="" syncable="YES"/> + <attribute name="slug" attributeType="String" syncable="YES"/> + <attribute name="title" optional="YES" attributeType="String" syncable="YES"/> + <relationship name="categories" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="PageTemplateCategory" inverseName="layouts" inverseEntity="PageTemplateCategory" syncable="YES"/> + </entity> + <entity name="Person" representedClassName=".ManagedPerson" syncable="YES"> + <attribute name="avatarURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="creationDate" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="displayName" attributeType="String" syncable="YES"/> + <attribute name="firstName" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="isSuperAdmin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="kind" optional="YES" attributeType="Integer 16" usesScalarValueType="NO" syncable="YES"/> + <attribute name="lastName" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="linkedUserID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="role" attributeType="String" syncable="YES"/> + <attribute name="siteID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="userID" attributeType="Integer 64" usesScalarValueType="NO" syncable="YES"/> + <attribute name="username" attributeType="String" syncable="YES"/> + </entity> + <entity name="Plan" representedClassName=".Plan" syncable="YES"> + <attribute name="features" attributeType="String" syncable="YES"/> + <attribute name="groups" attributeType="String" syncable="YES"/> + <attribute name="icon" attributeType="String" syncable="YES"/> + <attribute name="name" attributeType="String" syncable="YES"/> + <attribute name="nonLocalizedShortname" attributeType="String" defaultValueString="" syncable="YES"/> + <attribute name="order" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="products" attributeType="String" syncable="YES"/> + <attribute name="shortname" attributeType="String" syncable="YES"/> + <attribute name="summary" attributeType="String" syncable="YES"/> + <attribute name="supportName" attributeType="String" defaultValueString="" syncable="YES"/> + <attribute name="supportPriority" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="tagline" attributeType="String" syncable="YES"/> + </entity> + <entity name="PlanFeature" representedClassName=".PlanFeature" syncable="YES"> + <attribute name="slug" attributeType="String" syncable="YES"/> + <attribute name="summary" attributeType="String" syncable="YES"/> + <attribute name="title" attributeType="String" syncable="YES"/> + </entity> + <entity name="PlanGroup" representedClassName=".PlanGroup" syncable="YES"> + <attribute name="name" attributeType="String" syncable="YES"/> + <attribute name="order" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="slug" attributeType="String" syncable="YES"/> + </entity> + <entity name="Post" representedClassName="Post" parentEntity="AbstractPost"> + <attribute name="commentCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="disabledPublicizeConnections" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData"/> + <attribute name="geolocation" optional="YES" attributeType="Transformable" valueTransformerName="CoordinateValueTransformer"/> + <attribute name="isStickyPost" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="latitudeID" optional="YES" attributeType="String"/> + <attribute name="likeCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="longitudeID" optional="YES" attributeType="String"/> + <attribute name="postFormat" optional="YES" attributeType="String"/> + <attribute name="postType" attributeType="String" defaultValueString="post" syncable="YES"/> + <attribute name="publicID" optional="YES" attributeType="String"/> + <attribute name="publicizeMessage" optional="YES" attributeType="String"/> + <attribute name="publicizeMessageID" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="tags" optional="YES" attributeType="String"/> + <relationship name="categories" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Category" inverseName="posts" inverseEntity="Category"/> + <fetchIndex name="byCategoriesIndex"> + <fetchIndexElement property="categories" type="Binary" order="ascending"/> + </fetchIndex> + <userInfo/> + </entity> + <entity name="PostTag" representedClassName="PostTag"> + <attribute name="name" attributeType="String"/> + <attribute name="postCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="slug" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="tagDescription" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="tagID" optional="YES" attributeType="Integer 32" defaultValueString="-1" usesScalarValueType="NO"/> + <relationship name="blog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="tags" inverseEntity="Blog" syncable="YES"/> + <userInfo/> + </entity> + <entity name="PostType" representedClassName="PostType" syncable="YES"> + <attribute name="apiQueryable" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="label" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="name" optional="YES" attributeType="String" syncable="YES"/> + <relationship name="blog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="postTypes" inverseEntity="Blog" syncable="YES"/> + </entity> + <entity name="PublicizeConnection" representedClassName="WordPress.PublicizeConnection" syncable="YES"> + <attribute name="connectionID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="dateExpires" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="dateIssued" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="externalDisplay" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="externalFollowerCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="externalID" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="externalName" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="externalProfilePicture" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="externalProfileURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="keyringConnectionID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="keyringConnectionUserID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="label" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="refreshURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="service" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="shared" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="siteID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="status" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="userID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <relationship name="blog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="connections" inverseEntity="Blog" syncable="YES"/> + </entity> + <entity name="PublicizeConnectionStatsRecordValue" representedClassName=".PublicizeConnectionStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="followersCount" attributeType="Integer 64" usesScalarValueType="YES" syncable="YES"/> + <attribute name="iconURLString" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="name" attributeType="String" syncable="YES"/> + </entity> + <entity name="PublicizeService" representedClassName="WordPress.PublicizeService" syncable="YES"> + <attribute name="connectURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="detail" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="externalUsersOnly" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="icon" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="jetpackModuleRequired" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="jetpackSupport" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="label" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="multipleExternalUserIDSupport" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="order" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="serviceID" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="type" optional="YES" attributeType="String" syncable="YES"/> + </entity> + <entity name="QuickStartTourState" representedClassName="QuickStartTourState" syncable="YES"> + <attribute name="completed" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="skipped" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="tourID" optional="YES" attributeType="String" syncable="YES"/> + <relationship name="blog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="quickStartTours" inverseEntity="Blog" syncable="YES"/> + </entity> + <entity name="ReaderAbstractTopic" representedClassName="WordPress.ReaderAbstractTopic" isAbstract="YES" syncable="YES"> + <attribute name="algorithm" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="following" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="inUse" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="lastSynced" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="path" attributeType="String" syncable="YES"/> + <attribute name="showInMenu" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="title" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="type" optional="YES" attributeType="String" syncable="YES"/> + <relationship name="posts" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="ReaderPost" inverseName="topic" inverseEntity="ReaderPost" syncable="YES"/> + <fetchIndex name="byInUseIndex"> + <fetchIndexElement property="inUse" type="Binary" order="ascending"/> + </fetchIndex> + <fetchIndex name="byPathIndex"> + <fetchIndexElement property="path" type="Binary" order="ascending"/> + </fetchIndex> + </entity> + <entity name="ReaderCard" representedClassName=".ReaderCard" syncable="YES"> + <attribute name="sortRank" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/> + <relationship name="post" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ReaderPost" inverseName="card" inverseEntity="ReaderPost" syncable="YES"/> + <relationship name="sites" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ReaderSiteTopic" inverseName="cards" inverseEntity="ReaderSiteTopic" syncable="YES"/> + <relationship name="topics" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ReaderTagTopic" inverseName="cards" inverseEntity="ReaderTagTopic" syncable="YES"/> + </entity> + <entity name="ReaderCrossPostMeta" representedClassName="WordPress.ReaderCrossPostMeta" syncable="YES"> + <attribute name="commentURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="postID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="postURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="siteID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="siteURL" optional="YES" attributeType="String" syncable="YES"/> + <relationship name="post" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ReaderPost" inverseName="crossPostMeta" inverseEntity="ReaderPost" syncable="YES"/> + </entity> + <entity name="ReaderDefaultTopic" representedClassName="WordPress.ReaderDefaultTopic" parentEntity="ReaderAbstractTopic" syncable="YES"/> + <entity name="ReaderGapMarker" representedClassName="ReaderGapMarker" parentEntity="ReaderPost" syncable="YES"/> + <entity name="ReaderListTopic" representedClassName="WordPress.ReaderListTopic" parentEntity="ReaderAbstractTopic" syncable="YES"> + <attribute name="isOwner" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isPublic" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="listDescription" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="listID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="owner" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="slug" optional="YES" attributeType="String" syncable="YES"/> + </entity> + <entity name="ReaderPost" representedClassName="ReaderPost" parentEntity="BasePost" syncable="YES"> + <attribute name="authorDisplayName" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="authorEmail" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="authorURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="blogDescription" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="blogName" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="blogURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="commentCount" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="commentsOpen" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="dateSynced" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="featuredImage" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="feedID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="feedItemID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="globalID" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="inUse" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isBlogAtomic" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isBlogPrivate" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isExternal" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isFollowing" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isJetpack" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isLiked" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isLikesEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isReblogged" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isSavedForLater" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isSeen" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/> + <attribute name="isSeenSupported" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/> + <attribute name="isSharingEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isSiteBlocked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isWPCom" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="likeCount" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="organizationID" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="postAvatar" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="primaryTag" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="primaryTagSlug" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="railcar" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="readingTime" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="score" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="siteIconURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="siteID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="sortDate" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="sortRank" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="summary" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="tags" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="wordCount" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <relationship name="card" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="ReaderCard" inverseName="post" inverseEntity="ReaderCard" syncable="YES"/> + <relationship name="crossPostMeta" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="ReaderCrossPostMeta" inverseName="post" inverseEntity="ReaderCrossPostMeta" syncable="YES"/> + <relationship name="sourceAttribution" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="SourcePostAttribution" inverseName="post" inverseEntity="SourcePostAttribution" syncable="YES"/> + <relationship name="topic" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ReaderAbstractTopic" inverseName="posts" inverseEntity="ReaderAbstractTopic" syncable="YES"/> + <fetchIndex name="byDateSyncedIndex"> + <fetchIndexElement property="dateSynced" type="Binary" order="ascending"/> + </fetchIndex> + <fetchIndex name="byGlobalIDIndex"> + <fetchIndexElement property="globalID" type="Binary" order="ascending"/> + </fetchIndex> + <fetchIndex name="byInUseIndex"> + <fetchIndexElement property="inUse" type="Binary" order="ascending"/> + </fetchIndex> + <fetchIndex name="byIsSiteBlockedIndex"> + <fetchIndexElement property="isSiteBlocked" type="Binary" order="ascending"/> + </fetchIndex> + <fetchIndex name="bySiteIDIndex"> + <fetchIndexElement property="siteID" type="Binary" order="ascending"/> + </fetchIndex> + <fetchIndex name="bySortDateIndex"> + <fetchIndexElement property="sortDate" type="Binary" order="ascending"/> + </fetchIndex> + <fetchIndex name="bySortRankIndex"> + <fetchIndexElement property="sortRank" type="Binary" order="ascending"/> + </fetchIndex> + </entity> + <entity name="ReaderSearchSuggestion" representedClassName="WordPress.ReaderSearchSuggestion" syncable="YES"> + <attribute name="date" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="searchPhrase" attributeType="String" syncable="YES"/> + <fetchIndex name="byDateIndex"> + <fetchIndexElement property="date" type="Binary" order="ascending"/> + </fetchIndex> + <fetchIndex name="bySearchPhraseIndex"> + <fetchIndexElement property="searchPhrase" type="Binary" order="ascending"/> + </fetchIndex> + </entity> + <entity name="ReaderSearchTopic" representedClassName="WordPress.ReaderSearchTopic" parentEntity="ReaderAbstractTopic" syncable="YES"/> + <entity name="ReaderSiteInfoSubscriptionEmail" representedClassName="WordPress.ReaderSiteInfoSubscriptionEmail" syncable="YES"> + <attribute name="postDeliveryFrequency" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="sendComments" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="sendPosts" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <relationship name="siteTopic" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ReaderSiteTopic" inverseName="emailSubscription" inverseEntity="ReaderSiteTopic" syncable="YES"/> + </entity> + <entity name="ReaderSiteInfoSubscriptionPost" representedClassName="WordPress.ReaderSiteInfoSubscriptionPost" syncable="YES"> + <attribute name="sendPosts" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <relationship name="siteTopic" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ReaderSiteTopic" inverseName="postSubscription" inverseEntity="ReaderSiteTopic" syncable="YES"/> + </entity> + <entity name="ReaderSiteTopic" representedClassName="WordPress.ReaderSiteTopic" parentEntity="ReaderAbstractTopic" syncable="YES"> + <attribute name="feedID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="feedURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="isJetpack" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isPrivate" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="isVisible" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="organizationID" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="postCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="siteBlavatar" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="siteDescription" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="siteID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="siteURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="subscriberCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="unseenCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <relationship name="cards" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="ReaderCard" inverseName="sites" inverseEntity="ReaderCard" syncable="YES"/> + <relationship name="emailSubscription" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="ReaderSiteInfoSubscriptionEmail" inverseName="siteTopic" inverseEntity="ReaderSiteInfoSubscriptionEmail" syncable="YES"/> + <relationship name="postSubscription" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="ReaderSiteInfoSubscriptionPost" inverseName="siteTopic" inverseEntity="ReaderSiteInfoSubscriptionPost" syncable="YES"/> + </entity> + <entity name="ReaderTagTopic" representedClassName="WordPress.ReaderTagTopic" parentEntity="ReaderAbstractTopic" syncable="YES"> + <attribute name="isRecommended" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="slug" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="tagID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <relationship name="cards" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="ReaderCard" inverseName="topics" inverseEntity="ReaderCard" syncable="YES"/> + </entity> + <entity name="ReaderTeamTopic" representedClassName="WordPress.ReaderTeamTopic" parentEntity="ReaderAbstractTopic" syncable="YES"> + <attribute name="organizationID" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="slug" optional="YES" attributeType="String" syncable="YES"/> + </entity> + <entity name="ReferrerStatsRecordValue" representedClassName=".ReferrerStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="iconURLString" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="label" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="urlString" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="viewsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <relationship name="children" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="ReferrerStatsRecordValue" inverseName="parent" inverseEntity="ReferrerStatsRecordValue" syncable="YES"/> + <relationship name="parent" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ReferrerStatsRecordValue" inverseName="children" inverseEntity="ReferrerStatsRecordValue" syncable="YES"/> + </entity> + <entity name="Revision" representedClassName="WordPress.Revision" syncable="YES"> + <attribute name="postAuthorId" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="postContent" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="postDateGmt" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="postExcerpt" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="postId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="postModifiedGmt" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="postTitle" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="revisionId" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="siteId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <relationship name="diff" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="RevisionDiff" inverseName="revision" inverseEntity="RevisionDiff" syncable="YES"/> + </entity> + <entity name="RevisionDiff" representedClassName="WordPress.RevisionDiff" syncable="YES"> + <attribute name="fromRevisionId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="toRevisionId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="totalAdditions" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="totalDeletions" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <relationship name="contentDiffs" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="DiffContentValue" inverseName="revisionDiff" inverseEntity="DiffContentValue" syncable="YES"/> + <relationship name="revision" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Revision" inverseName="diff" inverseEntity="Revision" syncable="YES"/> + <relationship name="titleDiffs" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="DiffTitleValue" inverseName="revisionDiff" inverseEntity="DiffTitleValue" syncable="YES"/> + </entity> + <entity name="Role" representedClassName=".Role" syncable="YES"> + <attribute name="name" attributeType="String" syncable="YES"/> + <attribute name="order" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="slug" attributeType="String" syncable="YES"/> + <relationship name="blog" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="roles" inverseEntity="Blog" syncable="YES"/> + </entity> + <entity name="SearchResultsStatsRecordValue" representedClassName=".SearchResultsStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="searchTerm" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="viewsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + </entity> + <entity name="SharingButton" representedClassName="WordPress.SharingButton" syncable="YES"> + <attribute name="buttonID" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="custom" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="name" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="order" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="shortname" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="visibility" optional="YES" attributeType="String" syncable="YES"/> + <relationship name="blog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="sharingButtons" inverseEntity="Blog" syncable="YES"/> + <fetchIndex name="byOrderIndex"> + <fetchIndexElement property="order" type="Binary" order="ascending"/> + </fetchIndex> + </entity> + <entity name="SiteSuggestion" representedClassName="SiteSuggestion" syncable="YES"> + <attribute name="blavatarURL" optional="YES" attributeType="URI" syncable="YES"/> + <attribute name="siteURL" optional="YES" attributeType="URI" syncable="YES"/> + <attribute name="subdomain" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="title" optional="YES" attributeType="String" syncable="YES"/> + <relationship name="blog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="siteSuggestions" inverseEntity="Blog" syncable="YES"/> + </entity> + <entity name="SourcePostAttribution" representedClassName="SourcePostAttribution" syncable="YES"> + <attribute name="attributionType" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="authorName" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="authorURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="avatarURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="blogID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="blogName" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="blogURL" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="commentCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="likeCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="permalink" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="postID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <relationship name="post" maxCount="1" deletionRule="Nullify" destinationEntity="ReaderPost" inverseName="sourceAttribution" inverseEntity="ReaderPost" syncable="YES"/> + </entity> + <entity name="StatsRecord" representedClassName="WordPress.StatsRecord" syncable="YES"> + <attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="fetchedDate" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="period" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="type" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <relationship name="blog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="statsRecords" inverseEntity="Blog" syncable="YES"/> + <relationship name="values" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="StatsRecordValue" inverseName="statsRecord" inverseEntity="StatsRecordValue" syncable="YES"/> + </entity> + <entity name="StatsRecordValue" representedClassName="WordPress.StatsRecordValue" isAbstract="YES" syncable="YES"> + <relationship name="statsRecord" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StatsRecord" inverseName="values" inverseEntity="StatsRecord" syncable="YES"/> + </entity> + <entity name="StreakInsightStatsRecordValue" representedClassName=".StreakInsightStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="currentStreakEnd" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="currentStreakLength" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="currentStreakStart" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="longestStreakEnd" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="longestStreakLength" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="longestStreakStart" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <relationship name="streakData" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="StreakStatsRecordValue" inverseName="streakInsight" inverseEntity="StreakStatsRecordValue" syncable="YES"/> + </entity> + <entity name="StreakStatsRecordValue" representedClassName=".StreakStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="postCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <relationship name="streakInsight" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StreakInsightStatsRecordValue" inverseName="streakData" inverseEntity="StreakInsightStatsRecordValue" syncable="YES"/> + </entity> + <entity name="TagsCategoriesStatsRecordValue" representedClassName=".TagsCategoriesStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="name" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="type" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="urlString" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="viewsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <relationship name="children" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="TagsCategoriesStatsRecordValue" inverseName="parent" inverseEntity="TagsCategoriesStatsRecordValue" syncable="YES"/> + <relationship name="parent" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TagsCategoriesStatsRecordValue" inverseName="children" inverseEntity="TagsCategoriesStatsRecordValue" syncable="YES"/> + </entity> + <entity name="Theme" representedClassName="Theme" syncable="YES"> + <attribute name="author" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="authorUrl" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="custom" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/> + <attribute name="demoUrl" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="details" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="launchDate" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="name" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="order" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="popularityRank" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="premium" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="previewUrl" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="price" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="purchased" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/> + <attribute name="screenshotUrl" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="stylesheet" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="tags" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData" syncable="YES"/> + <attribute name="themeId" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="themeUrl" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="trendingRank" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> + <attribute name="version" optional="YES" attributeType="String" syncable="YES"/> + <relationship name="blog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="themes" inverseEntity="Blog" syncable="YES"/> + </entity> + <entity name="TodayStatsRecordValue" representedClassName=".TodayStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="commentsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="likesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="viewsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="visitorsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + </entity> + <entity name="TopCommentedPostStatsRecordValue" representedClassName=".TopCommentedPostStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="commentCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="postID" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="postURLString" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="title" optional="YES" attributeType="String" syncable="YES"/> + </entity> + <entity name="TopCommentsAuthorStatsRecordValue" representedClassName=".TopCommentsAuthorStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="avatarURLString" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="commentCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="name" optional="YES" attributeType="String" syncable="YES"/> + </entity> + <entity name="TopViewedAuthorStatsRecordValue" representedClassName=".TopViewedAuthorStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="avatarURLString" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="name" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="viewsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <relationship name="posts" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TopViewedPostStatsRecordValue" inverseName="author" inverseEntity="TopViewedPostStatsRecordValue" syncable="YES"/> + </entity> + <entity name="TopViewedPostStatsRecordValue" representedClassName=".TopViewedPostStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="postID" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="postURLString" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="title" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="type" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="viewsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <relationship name="author" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TopViewedAuthorStatsRecordValue" inverseName="posts" inverseEntity="TopViewedAuthorStatsRecordValue" syncable="YES"/> + </entity> + <entity name="TopViewedVideoStatsRecordValue" representedClassName=".TopViewedVideoStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="playsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="postID" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="postURLString" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="title" optional="YES" attributeType="String" syncable="YES"/> + </entity> + <entity name="UserSuggestion" representedClassName="UserSuggestion" syncable="YES"> + <attribute name="displayName" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="imageURL" optional="YES" attributeType="URI" syncable="YES"/> + <attribute name="username" optional="YES" attributeType="String" syncable="YES"/> + <relationship name="blog" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="userSuggestions" inverseEntity="Blog" syncable="YES"/> + </entity> + <entity name="VisitsSummaryStatsRecordValue" representedClassName=".VisitsSummaryStatsRecordValue" parentEntity="StatsRecordValue" syncable="YES"> + <attribute name="commentsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="likesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="periodStart" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> + <attribute name="viewsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + <attribute name="visitorsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/> + </entity> + <elements> + <element name="AbstractPost" positionX="-867.89453125" positionY="-2.7890625" width="128" height="313"/> + <element name="Account" positionX="-152.04296875" positionY="-313.31640625" width="128" height="223"/> + <element name="AccountSettings" positionX="-146.67578125" positionY="-608.9609375" width="128" height="254"/> + <element name="AllTimeStatsRecordValue" positionX="323.8671875" positionY="1792.16015625" width="128" height="118"/> + <element name="AnnualAndMostPopularTimeStatsRecordValue" positionX="5.7890625" positionY="1972.53515625" width="128" height="253"/> + <element name="BasePost" positionX="-1100.31640625" positionY="-317.4140625" width="128" height="285"/> + <element name="Blog" positionX="315.19921875" positionY="-178.44921875" width="188.421875" height="854"/> + <element name="BlogAuthor" positionX="564.83203125" positionY="-385.12109375" width="128" height="165"/> + <element name="BlogSettings" positionX="1056.86328125" positionY="236.3984375" width="128" height="853"/> + <element name="Category" positionX="-472.19921875" positionY="-178.47265625" width="128" height="118"/> + <element name="ClicksStatsRecordValue" positionX="493.99609375" positionY="1741.6171875" width="128" height="133"/> + <element name="Comment" positionX="-897.9921875" positionY="-561.09375" width="128" height="343"/> + <element name="CountryStatsRecordValue" positionX="321.078125" positionY="2037.06640625" width="128" height="88"/> + <element name="DiffAbstractValue" positionX="-1074.79296875" positionY="1237.0859375" width="128" height="88"/> + <element name="DiffContentValue" positionX="-1280.3125" positionY="1246.609375" width="128" height="60"/> + <element name="DiffTitleValue" positionX="-882.671875" positionY="1250.9296875" width="128" height="60"/> + <element name="Domain" positionX="888.98046875" positionY="-271.15625" width="128" height="105"/> + <element name="FileDownloadsStatsRecordValue" positionX="-136.03125" positionY="1400.13671875" width="128" height="73"/> + <element name="FollowersCountStatsRecordValue" positionX="-291.12109375" positionY="2134.55078125" width="128" height="73"/> + <element name="FollowersStatsRecordValue" positionX="158.42578125" positionY="2028.71875" width="128" height="103"/> + <element name="InviteLinks" positionX="-1638" positionY="-459" width="128" height="163"/> + <element name="LastPostStatsRecordValue" positionX="-141.33203125" positionY="1970.5078125" width="128" height="148"/> + <element name="Media" positionX="-155.23046875" positionY="83.17578125" width="128" height="433"/> + <element name="Menu" positionX="1276.01953125" positionY="-190.328125" width="128" height="135"/> + <element name="MenuItem" positionX="1261.1484375" positionY="-6.04296875" width="128" height="255"/> + <element name="MenuLocation" positionX="1072.3828125" positionY="-235.34375" width="128" height="120"/> + <element name="Notification" positionX="-1667.6015625" positionY="241.66796875" width="128" height="240"/> + <element name="OtherAndTotalViewsCountStatsRecordValue" positionX="6.0859375" positionY="1399.26953125" width="128" height="75"/> + <element name="Page" positionX="-854.90625" positionY="-144.5234375" width="128" height="60"/> + <element name="PageTemplateCategory" positionX="-1638" positionY="-459" width="128" height="148"/> + <element name="PageTemplateLayout" positionX="-1629" positionY="-450" width="128" height="163"/> + <element name="Person" positionX="-1825.45703125" positionY="-16.94140625" width="128" height="225"/> + <element name="Plan" positionX="-1802.80859375" positionY="-272.67578125" width="128" height="223"/> + <element name="PlanFeature" positionX="-1656.703125" positionY="-166.43359375" width="128" height="90"/> + <element name="PlanGroup" positionX="-1656.13671875" positionY="-275.78515625" width="128" height="90"/> + <element name="Post" positionX="-678.7734375" positionY="-84.65625" width="128" height="253"/> + <element name="PostTag" positionX="577.68359375" positionY="407.53515625" width="128" height="135"/> + <element name="PostType" positionX="159.7578125" positionY="48.80859375" width="128" height="105"/> + <element name="PublicizeConnection" positionX="721.8203125" positionY="-528.5625" width="128" height="330"/> + <element name="PublicizeConnectionStatsRecordValue" positionX="328.82421875" positionY="1922.828125" width="128" height="90"/> + <element name="PublicizeService" positionX="-1660.05078125" positionY="-16.88671875" width="128" height="210"/> + <element name="QuickStartTourState" positionX="1069.09375" positionY="-43.8515625" width="128" height="105"/> + <element name="ReaderAbstractTopic" positionX="-1504.35546875" positionY="693.08984375" width="128" height="180"/> + <element name="ReaderCard" positionX="-1638" positionY="-459" width="128" height="103"/> + <element name="ReaderCrossPostMeta" positionX="-1292.6171875" positionY="353.91796875" width="128" height="135"/> + <element name="ReaderDefaultTopic" positionX="-1651.8125" positionY="578.61328125" width="128" height="45"/> + <element name="ReaderGapMarker" positionX="-1092.98828125" positionY="814.5703125" width="128" height="45"/> + <element name="ReaderListTopic" positionX="-1500.2890625" positionY="932.5703125" width="128" height="135"/> + <element name="ReaderPost" positionX="-1102" positionY="72.3046875" width="128" height="734"/> + <element name="ReaderSearchSuggestion" positionX="-1292.68359375" positionY="211.44921875" width="128" height="75"/> + <element name="ReaderSearchTopic" positionX="-1657.08984375" positionY="930.36328125" width="128" height="45"/> + <element name="ReaderSiteInfoSubscriptionEmail" positionX="-1832.21875" positionY="567.79296875" width="128" height="105"/> + <element name="ReaderSiteInfoSubscriptionPost" positionX="-1845.87109375" positionY="1035.71484375" width="128" height="75"/> + <element name="ReaderSiteTopic" positionX="-1831.04296875" positionY="737.4921875" width="128" height="283"/> + <element name="ReaderTagTopic" positionX="-1351.87890625" positionY="574.1953125" width="128" height="103"/> + <element name="ReaderTeamTopic" positionX="-1506.9375" positionY="576.81640625" width="128" height="73"/> + <element name="ReferrerStatsRecordValue" positionX="-273.765625" positionY="1398.2265625" width="128" height="133"/> + <element name="Revision" positionX="-1287.671875" positionY="1336.49609375" width="128" height="195"/> + <element name="RevisionDiff" positionX="-1077.3046875" positionY="1396.46875" width="128" height="150"/> + <element name="Role" positionX="1120.60546875" positionY="77.73046875" width="128" height="105"/> + <element name="SearchResultsStatsRecordValue" positionX="319.87109375" positionY="1576.28125" width="128" height="73"/> + <element name="SharingButton" positionX="635.53125" positionY="142.16796875" width="128" height="165"/> + <element name="SiteSuggestion" positionX="-1638" positionY="-459" width="128" height="118"/> + <element name="SourcePostAttribution" positionX="-929.09765625" positionY="565.2421875" width="128" height="225"/> + <element name="StatsRecord" positionX="475.08203125" positionY="1591.87109375" width="128" height="133"/> + <element name="StatsRecordValue" positionX="-97.88671875" positionY="1666.3125" width="128" height="60"/> + <element name="StreakInsightStatsRecordValue" positionX="319.28125" positionY="1409.96875" width="128" height="148"/> + <element name="StreakStatsRecordValue" positionX="152.14453125" positionY="1403.62109375" width="128" height="88"/> + <element name="TagsCategoriesStatsRecordValue" positionX="-476.7421875" positionY="1974.1484375" width="128" height="133"/> + <element name="Theme" positionX="331.85546875" positionY="219.17578125" width="128" height="358"/> + <element name="TodayStatsRecordValue" positionX="-641.8359375" positionY="1405.5078125" width="128" height="103"/> + <element name="TopCommentedPostStatsRecordValue" positionX="-640.5" positionY="1523.6015625" width="128" height="103"/> + <element name="TopCommentsAuthorStatsRecordValue" positionX="-486.2421875" positionY="1395.7890625" width="128" height="88"/> + <element name="TopViewedAuthorStatsRecordValue" positionX="-637.94921875" positionY="1803.6796875" width="128" height="105"/> + <element name="TopViewedPostStatsRecordValue" positionX="-638.76953125" positionY="1639.93359375" width="128" height="133"/> + <element name="TopViewedVideoStatsRecordValue" positionX="-638.37890625" positionY="1936.55859375" width="128" height="103"/> + <element name="UserSuggestion" positionX="160" positionY="192" width="128" height="103"/> + <element name="VisitsSummaryStatsRecordValue" positionX="-325.91015625" positionY="1975.75390625" width="128" height="118"/> + <element name="BlockEditorSettings" positionX="-1629" positionY="-441" width="128" height="104"/> + <element name="BlockEditorSettingElement" positionX="-1620" positionY="-432" width="128" height="104"/> + </elements> +</model> \ No newline at end of file diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 744eeb2f46c5..02e9dab2bb5e 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -328,7 +328,7 @@ 24B1AE3124FEC79900B9F334 /* RemoteFeatureFlagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24B1AE3024FEC79900B9F334 /* RemoteFeatureFlagTests.swift */; }; 24C69A8B2612421900312D9A /* UserSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24C69A8A2612421900312D9A /* UserSettingsTests.swift */; }; 24C69AC22612467C00312D9A /* UserSettingsTestsObjc.m in Sources */ = {isa = PBXBuildFile; fileRef = 24C69AC12612467C00312D9A /* UserSettingsTestsObjc.m */; }; - 24CE2EB1258D687A0000C297 /* WordPressFlux in Frameworks */ = {isa = PBXBuildFile; productRef = 24CE2EB0258D687A0000C297 /* WordPressFlux */; }; + 24CE2EB1258D687A0000C297 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = 24CE2EB0258D687A0000C297 /* SwiftPackageProductDependency */; }; 24F3789825E6E62100A27BB7 /* NSManagedObject+Lookup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F3789725E6E62100A27BB7 /* NSManagedObject+Lookup.swift */; }; 2611CC62A62F9E6BC25350FE /* Pods_WordPressScreenshotGeneration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB390AA9C94F16E78184E9D1 /* Pods_WordPressScreenshotGeneration.framework */; }; 26D66DEC36ACF7442186B07D /* Pods_WordPressThisWeekWidget.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 979B445A45E13F3289F2E99E /* Pods_WordPressThisWeekWidget.framework */; }; @@ -710,7 +710,6 @@ 46241C3C2540D483002B8A12 /* SiteDesignContentCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46241C3A2540D483002B8A12 /* SiteDesignContentCollectionViewController.swift */; }; 4625B556253789C000C04AAD /* CollapsableHeaderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4625B555253789C000C04AAD /* CollapsableHeaderViewController.swift */; }; 4625B6342538B53700C04AAD /* CollapsableHeaderViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4625B6332538B53700C04AAD /* CollapsableHeaderViewController.xib */; }; - 4629E41D243D21400002E15C /* EditorThemeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4629E41C243D21400002E15C /* EditorThemeStore.swift */; }; 4629E4212440C5B20002E15C /* GutenbergCoverUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4629E4202440C5B20002E15C /* GutenbergCoverUploadProcessor.swift */; }; 4629E4232440C8160002E15C /* GutenbergCoverUploadProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4629E4222440C8160002E15C /* GutenbergCoverUploadProcessorTests.swift */; }; 462F4E0A18369F0B0028D2F8 /* BlogDetailsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 462F4E0718369F0B0028D2F8 /* BlogDetailsViewController.m */; }; @@ -734,8 +733,26 @@ 46B30B782582C7DD00A25E66 /* SiteAddressServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B30B772582C7DD00A25E66 /* SiteAddressServiceTests.swift */; }; 46B30B872582CA2200A25E66 /* domain-suggestions.json in Resources */ = {isa = PBXBuildFile; fileRef = 46B30B862582CA2200A25E66 /* domain-suggestions.json */; }; 46C984682527863E00988BB9 /* LayoutPickerAnalyticsEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46C984672527863E00988BB9 /* LayoutPickerAnalyticsEvent.swift */; }; + 46CFA7BF262745F70077BAD9 /* get_wp_v2_themes_twentytwentyone.json in Resources */ = {isa = PBXBuildFile; fileRef = 46CFA7BE262745F70077BAD9 /* get_wp_v2_themes_twentytwentyone.json */; }; + 46CFA7E3262746940077BAD9 /* get_wp_v2_themes_twentytwenty.json in Resources */ = {isa = PBXBuildFile; fileRef = 46CFA7E2262746940077BAD9 /* get_wp_v2_themes_twentytwenty.json */; }; 46D6114F2555DAED00B0B7BB /* SiteCreationAnalyticsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46D6114E2555DAED00B0B7BB /* SiteCreationAnalyticsHelper.swift */; }; 46E327D124E705C7000944B3 /* PageLayoutService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46E327D024E705C7000944B3 /* PageLayoutService.swift */; }; + 46F583A92624CE790010A723 /* BlockEditorSettings+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F583A52624CE790010A723 /* BlockEditorSettings+CoreDataClass.swift */; }; + 46F583AA2624CE790010A723 /* BlockEditorSettings+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F583A52624CE790010A723 /* BlockEditorSettings+CoreDataClass.swift */; }; + 46F583AB2624CE790010A723 /* BlockEditorSettings+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F583A62624CE790010A723 /* BlockEditorSettings+CoreDataProperties.swift */; }; + 46F583AC2624CE790010A723 /* BlockEditorSettings+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F583A62624CE790010A723 /* BlockEditorSettings+CoreDataProperties.swift */; }; + 46F583AD2624CE790010A723 /* BlockEditorSettingElement+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F583A72624CE790010A723 /* BlockEditorSettingElement+CoreDataClass.swift */; }; + 46F583AE2624CE790010A723 /* BlockEditorSettingElement+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F583A72624CE790010A723 /* BlockEditorSettingElement+CoreDataClass.swift */; }; + 46F583AF2624CE790010A723 /* BlockEditorSettingElement+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F583A82624CE790010A723 /* BlockEditorSettingElement+CoreDataProperties.swift */; }; + 46F583B02624CE790010A723 /* BlockEditorSettingElement+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F583A82624CE790010A723 /* BlockEditorSettingElement+CoreDataProperties.swift */; }; + 46F583D42624D0BC0010A723 /* Blog+BlockEditorSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F583D32624D0BC0010A723 /* Blog+BlockEditorSettings.swift */; }; + 46F583D52624D0BC0010A723 /* Blog+BlockEditorSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F583D32624D0BC0010A723 /* Blog+BlockEditorSettings.swift */; }; + 46F584822624DCC80010A723 /* BlockEditorSettingsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F584812624DCC80010A723 /* BlockEditorSettingsService.swift */; }; + 46F584832624DCC80010A723 /* BlockEditorSettingsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F584812624DCC80010A723 /* BlockEditorSettingsService.swift */; }; + 46F584B82624E6380010A723 /* BlockEditorSettings+GutenbergEditorTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F584B72624E6380010A723 /* BlockEditorSettings+GutenbergEditorTheme.swift */; }; + 46F584B92624E6380010A723 /* BlockEditorSettings+GutenbergEditorTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F584B72624E6380010A723 /* BlockEditorSettings+GutenbergEditorTheme.swift */; }; + 46F584EE2625FF100010A723 /* EditorThemeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F584ED2625FF100010A723 /* EditorThemeTests.swift */; }; + 46F58501262605930010A723 /* BlockEditorSettingsServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F58500262605930010A723 /* BlockEditorSettingsServiceTests.swift */; }; 4B2DD0F29CD6AC353C056D41 /* Pods_WordPressUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DCE7542239FBC709B90EA85 /* Pods_WordPressUITests.framework */; }; 4C8A715EBCE7E73AEE216293 /* Pods_WordPressShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F47DB4A8EC2E6844E213A3FA /* Pods_WordPressShareExtension.framework */; }; 4D520D4F22972BC9002F5924 /* acknowledgements.html in Resources */ = {isa = PBXBuildFile; fileRef = 4D520D4E22972BC9002F5924 /* acknowledgements.html */; }; @@ -2975,7 +2992,6 @@ FABB21E92602FC2C00C8785C /* MenuItemSourceHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 08D978521CD2AF7D0054F19A /* MenuItemSourceHeaderView.m */; }; FABB21EA2602FC2C00C8785C /* RestoreWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA1A543D25A6E2F60033967D /* RestoreWarningView.swift */; }; FABB21EB2602FC2C00C8785C /* GutenbergWebNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4F2E702458AF8500EB73E7 /* GutenbergWebNavigationViewController.swift */; }; - FABB21EC2602FC2C00C8785C /* EditorThemeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4629E41C243D21400002E15C /* EditorThemeStore.swift */; }; FABB21ED2602FC2C00C8785C /* WPTabBarController+ReaderTabNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF1A852242D5FCB00373F5D /* WPTabBarController+ReaderTabNavigation.swift */; }; FABB21EE2602FC2C00C8785C /* WPStyleGuide+Blog.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE1071FB1BC75E7400906AFF /* WPStyleGuide+Blog.swift */; }; FABB21EF2602FC2C00C8785C /* KeyboardDismissHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56695AF1D411EEB007E342F /* KeyboardDismissHelper.swift */; }; @@ -4070,7 +4086,7 @@ FABB26322602FC2C00C8785C /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 296890770FE971DC00770264 /* Security.framework */; }; FABB26332602FC2C00C8785C /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83F3E25F11275E07004CD686 /* MapKit.framework */; }; FABB26342602FC2C00C8785C /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83F3E2D211276371004CD686 /* CoreLocation.framework */; }; - FABB26352602FC2C00C8785C /* WordPressFlux in Frameworks */ = {isa = PBXBuildFile; productRef = FABB1FA62602FC2C00C8785C /* WordPressFlux */; }; + FABB26352602FC2C00C8785C /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = FABB1FA62602FC2C00C8785C /* SwiftPackageProductDependency */; }; FABB26362602FC2C00C8785C /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8355D7D811D260AA00A61362 /* CoreData.framework */; }; FABB26372602FC2C00C8785C /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 834CE7331256D0DE0046A4A3 /* CFNetwork.framework */; }; FABB26382602FC2C00C8785C /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83043E54126FA31400EC9953 /* MessageUI.framework */; }; @@ -5076,7 +5092,6 @@ 4625B555253789C000C04AAD /* CollapsableHeaderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsableHeaderViewController.swift; sourceTree = "<group>"; }; 4625B6332538B53700C04AAD /* CollapsableHeaderViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CollapsableHeaderViewController.xib; sourceTree = "<group>"; }; 4625BC26253E285700C04AAD /* WordPress 102.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 102.xcdatamodel"; sourceTree = "<group>"; }; - 4629E41C243D21400002E15C /* EditorThemeStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorThemeStore.swift; sourceTree = "<group>"; }; 4629E4202440C5B20002E15C /* GutenbergCoverUploadProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutenbergCoverUploadProcessor.swift; sourceTree = "<group>"; }; 4629E4222440C8160002E15C /* GutenbergCoverUploadProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutenbergCoverUploadProcessorTests.swift; sourceTree = "<group>"; }; 462F4E0618369F0B0028D2F8 /* BlogDetailsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlogDetailsViewController.h; sourceTree = "<group>"; }; @@ -5102,8 +5117,20 @@ 46B30B772582C7DD00A25E66 /* SiteAddressServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteAddressServiceTests.swift; sourceTree = "<group>"; }; 46B30B862582CA2200A25E66 /* domain-suggestions.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "domain-suggestions.json"; sourceTree = "<group>"; }; 46C984672527863E00988BB9 /* LayoutPickerAnalyticsEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutPickerAnalyticsEvent.swift; sourceTree = "<group>"; }; + 46CFA7BE262745F70077BAD9 /* get_wp_v2_themes_twentytwentyone.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = get_wp_v2_themes_twentytwentyone.json; sourceTree = "<group>"; }; + 46CFA7E2262746940077BAD9 /* get_wp_v2_themes_twentytwenty.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = get_wp_v2_themes_twentytwenty.json; sourceTree = "<group>"; }; 46D6114E2555DAED00B0B7BB /* SiteCreationAnalyticsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteCreationAnalyticsHelper.swift; sourceTree = "<group>"; }; 46E327D024E705C7000944B3 /* PageLayoutService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageLayoutService.swift; sourceTree = "<group>"; }; + 46F583A42624C8FA0010A723 /* WordPress 120.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 120.xcdatamodel"; sourceTree = "<group>"; }; + 46F583A52624CE790010A723 /* BlockEditorSettings+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BlockEditorSettings+CoreDataClass.swift"; sourceTree = "<group>"; }; + 46F583A62624CE790010A723 /* BlockEditorSettings+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BlockEditorSettings+CoreDataProperties.swift"; sourceTree = "<group>"; }; + 46F583A72624CE790010A723 /* BlockEditorSettingElement+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BlockEditorSettingElement+CoreDataClass.swift"; sourceTree = "<group>"; }; + 46F583A82624CE790010A723 /* BlockEditorSettingElement+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BlockEditorSettingElement+CoreDataProperties.swift"; sourceTree = "<group>"; }; + 46F583D32624D0BC0010A723 /* Blog+BlockEditorSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Blog+BlockEditorSettings.swift"; sourceTree = "<group>"; }; + 46F584812624DCC80010A723 /* BlockEditorSettingsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockEditorSettingsService.swift; sourceTree = "<group>"; }; + 46F584B72624E6380010A723 /* BlockEditorSettings+GutenbergEditorTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BlockEditorSettings+GutenbergEditorTheme.swift"; sourceTree = "<group>"; }; + 46F584ED2625FF100010A723 /* EditorThemeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorThemeTests.swift; sourceTree = "<group>"; }; + 46F58500262605930010A723 /* BlockEditorSettingsServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockEditorSettingsServiceTests.swift; sourceTree = "<group>"; }; 46F84612185A8B7E009D0DA5 /* PostContentProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PostContentProvider.h; sourceTree = "<group>"; }; 48690E659987FD4472EEDE5F /* Pods-WordPressNotificationContentExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressNotificationContentExtension.release.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressNotificationContentExtension/Pods-WordPressNotificationContentExtension.release.xcconfig"; sourceTree = "<group>"; }; 4D520D4E22972BC9002F5924 /* acknowledgements.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = acknowledgements.html; path = "../Pods/Target Support Files/Pods-Apps-WordPress/acknowledgements.html"; sourceTree = "<group>"; }; @@ -7262,7 +7289,7 @@ 296890780FE971DC00770264 /* Security.framework in Frameworks */, 83F3E26011275E07004CD686 /* MapKit.framework in Frameworks */, 83F3E2D311276371004CD686 /* CoreLocation.framework in Frameworks */, - 24CE2EB1258D687A0000C297 /* WordPressFlux in Frameworks */, + 24CE2EB1258D687A0000C297 /* BuildFile in Frameworks */, 8355D7D911D260AA00A61362 /* CoreData.framework in Frameworks */, 834CE7341256D0DE0046A4A3 /* CFNetwork.framework in Frameworks */, 83043E55126FA31400EC9953 /* MessageUI.framework in Frameworks */, @@ -7396,7 +7423,7 @@ FABB26322602FC2C00C8785C /* Security.framework in Frameworks */, FABB26332602FC2C00C8785C /* MapKit.framework in Frameworks */, FABB26342602FC2C00C8785C /* CoreLocation.framework in Frameworks */, - FABB26352602FC2C00C8785C /* WordPressFlux in Frameworks */, + FABB26352602FC2C00C8785C /* BuildFile in Frameworks */, FABB26362602FC2C00C8785C /* CoreData.framework in Frameworks */, FABB26372602FC2C00C8785C /* CFNetwork.framework in Frameworks */, FABB26382602FC2C00C8785C /* MessageUI.framework in Frameworks */, @@ -8005,7 +8032,7 @@ path = GutenbergWeb; sourceTree = "<group>"; }; - 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { + 29B97314FDCFA39411CA2CEA = { isa = PBXGroup; children = ( F14B5F6F208E648200439554 /* config */, @@ -9234,6 +9261,11 @@ 46963F5724649509000D356D /* Gutenberg */ = { isa = PBXGroup; children = ( + 46F583A72624CE790010A723 /* BlockEditorSettingElement+CoreDataClass.swift */, + 46F583A82624CE790010A723 /* BlockEditorSettingElement+CoreDataProperties.swift */, + 46F583A52624CE790010A723 /* BlockEditorSettings+CoreDataClass.swift */, + 46F583A62624CE790010A723 /* BlockEditorSettings+CoreDataProperties.swift */, + 46F584B72624E6380010A723 /* BlockEditorSettings+GutenbergEditorTheme.swift */, 46963F5824649542000D356D /* EditorTheme.swift */, 46183D1D251BD6A0004F9AFD /* PageTemplateCategory+CoreDataClass.swift */, 46183D1E251BD6A0004F9AFD /* PageTemplateCategory+CoreDataProperties.swift */, @@ -9243,6 +9275,15 @@ name = Gutenberg; sourceTree = "<group>"; }; + 46CFA7BD262745A50077BAD9 /* BlockEditorSettings and Styles */ = { + isa = PBXGroup; + children = ( + 46CFA7BE262745F70077BAD9 /* get_wp_v2_themes_twentytwentyone.json */, + 46CFA7E2262746940077BAD9 /* get_wp_v2_themes_twentytwenty.json */, + ); + name = "BlockEditorSettings and Styles"; + sourceTree = "<group>"; + }; 46E327D524E71B2F000944B3 /* Page Layouts */ = { isa = PBXGroup; children = ( @@ -9493,6 +9534,7 @@ children = ( F1B1E7A224098FA100549E2A /* BlogTests.swift */, 246D0A0225E97D5D0028B83F /* Blog+ObjcTests.m */, + 46F584ED2625FF100010A723 /* EditorThemeTests.swift */, D848CC1620FF38EA00A9038F /* FormattableCommentRangeTests.swift */, D848CC1420FF33FC00A9038F /* NotificationContentRangeTests.swift */, 8BBBEBB124B8F8C0005E358E /* ReaderCardTests.swift */, @@ -10812,6 +10854,7 @@ E1FD45DF1C030B3800750F4C /* AccountSettingsService.swift */, F12FA5D82428FA8F0054DA21 /* AuthenticationService.swift */, F1ADCAF6241FEF0C00F150D2 /* AtomicAuthenticationService.swift */, + 46F584812624DCC80010A723 /* BlockEditorSettingsService.swift */, 822D60B81F4CCC7A0016C46D /* BlogJetpackSettingsService.swift */, 93C1148318EDF6E100DAC95C /* BlogService.h */, 8B749E7125AF522900023F03 /* JetpackCapabilitiesService.swift */, @@ -11639,6 +11682,7 @@ CEBD3EA90FF1BA3B00C1396E /* Blog.h */, CEBD3EAA0FF1BA3B00C1396E /* Blog.m */, E17FEAD9221494B2006E1D2D /* Blog+Analytics.swift */, + 46F583D32624D0BC0010A723 /* Blog+BlockEditorSettings.swift */, 9A341E5521997A330036662E /* Blog+BlogAuthors.swift */, 9A341E5421997A330036662E /* BlogAuthor.swift */, B518E1641CCAA19200ADFE75 /* Blog+Capabilities.swift */, @@ -12050,6 +12094,7 @@ children = ( 9363113E19FA996700B0C739 /* AccountServiceTests.swift */, F1F083F5241FFE930056D3B1 /* AtomicAuthenticationServiceTests.swift */, + 46F58500262605930010A723 /* BlockEditorSettingsServiceTests.swift */, E150520B16CAC5C400D3DDDC /* BlogJetpackTest.m */, 930FD0A519882742000CC81D /* BlogServiceTest.m */, E18549DA230FBFEF003C620E /* BlogServiceDeduplicationTests.swift */, @@ -13004,6 +13049,7 @@ E16AB94414D9A13A0047A2E5 /* Mock Data */ = { isa = PBXGroup; children = ( + 46CFA7BD262745A50077BAD9 /* BlockEditorSettings and Styles */, C8567490243F371D001A995E /* Tenor */, D8A468DE2181C5B50094B82F /* Site Creation */, 7E442FC820F6783600DEACA5 /* ActivityLog */, @@ -13138,7 +13184,6 @@ 9A2D0B24225CB97F009E585F /* JetpackInstallStore.swift */, 9A4E271C22EF33F5001F6A6B /* AccountSettingsStore.swift */, 9A09F914230C3E9700F42AB7 /* StoreFetchingStatus.swift */, - 4629E41C243D21400002E15C /* EditorThemeStore.swift */, 24ADA24B24F9A4CB001B5DAE /* RemoteFeatureFlagStore.swift */, 3F3CA64F25D3003C00642A89 /* StatsWidgetsStore.swift */, ); @@ -13886,7 +13931,7 @@ ); name = WordPress; packageProductDependencies = ( - 24CE2EB0258D687A0000C297 /* WordPressFlux */, + 24CE2EB0258D687A0000C297 /* SwiftPackageProductDependency */, ); productName = WordPress; productReference = 1D6058910D05DD3D006BFB54 /* WordPress.app */; @@ -14124,7 +14169,7 @@ ); name = Jetpack; packageProductDependencies = ( - FABB1FA62602FC2C00C8785C /* WordPressFlux */, + FABB1FA62602FC2C00C8785C /* SwiftPackageProductDependency */, ); productName = WordPress; productReference = FABB26522602FC2C00C8785C /* Jetpack.app */; @@ -14334,7 +14379,7 @@ bg, sk, ); - mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; + mainGroup = 29B97314FDCFA39411CA2CEA; productRefGroup = 19C28FACFE9D520D11CA2CBB /* Products */; projectDirPath = ""; projectRoot = ""; @@ -14817,6 +14862,7 @@ E15027611E03E51500B847E3 /* notes-action-delete.json in Resources */, 08F8CD361EBD2AA80049D0C0 /* test-image-device-photo-gps-portrait.jpg in Resources */, B5AEEC7D1ACACFDA008BF2A4 /* notifications-replied-comment.json in Resources */, + 46CFA7BF262745F70077BAD9 /* get_wp_v2_themes_twentytwentyone.json in Resources */, 3211055C250C027D0048446F /* valid-gif-header.gif in Resources */, D848CC0920FF2D4400A9038F /* notifications-icon-range.json in Resources */, E131CB5816CACFB4004B0314 /* get-user-blogs_doesnt-have-blog.json in Resources */, @@ -14825,6 +14871,7 @@ C8567494243F388F001A995E /* tenor-invalid-search-reponse.json in Resources */, 3211055B250C027D0048446F /* valid-jpeg-header.jpg in Resources */, B5AEEC791ACACFDA008BF2A4 /* notifications-badge.json in Resources */, + 46CFA7E3262746940077BAD9 /* get_wp_v2_themes_twentytwenty.json in Resources */, D848CC1120FF310400A9038F /* notifications-site-range.json in Resources */, 7E53AB0620FE6905005796FE /* activity-log-comment.json in Resources */, 93C882A41EEB18D700227A59 /* rsd.xml in Resources */, @@ -16258,7 +16305,6 @@ 08D978581CD2AF7D0054F19A /* MenuItemSourceHeaderView.m in Sources */, FA1A543E25A6E2F60033967D /* RestoreWarningView.swift in Sources */, 1E4F2E712458AF8500EB73E7 /* GutenbergWebNavigationViewController.swift in Sources */, - 4629E41D243D21400002E15C /* EditorThemeStore.swift in Sources */, 3FF1A853242D5FCB00373F5D /* WPTabBarController+ReaderTabNavigation.swift in Sources */, BE1071FC1BC75E7400906AFF /* WPStyleGuide+Blog.swift in Sources */, B56695B01D411EEB007E342F /* KeyboardDismissHelper.swift in Sources */, @@ -16605,6 +16651,7 @@ 439F4F3A219B715300F8D0C7 /* RevisionsNavigationController.swift in Sources */, 40F46B6A22121BA800A2143B /* AnnualAndMostPopularTimeStatsRecordValue+CoreDataClass.swift in Sources */, 17D5C3F71FFCF2D800EB70FF /* MediaProgressCoordinatorNoticeViewModel.swift in Sources */, + 46F584822624DCC80010A723 /* BlockEditorSettingsService.swift in Sources */, 984B139221F66AC60004B6A2 /* SiteStatsPeriodViewModel.swift in Sources */, 98A437AE20069098004A8A57 /* DomainSuggestionsTableViewController.swift in Sources */, 3F3087C424EDB7040087B548 /* AnnouncementCell.swift in Sources */, @@ -16675,6 +16722,7 @@ 5DF8D26119E82B1000A2CD95 /* ReaderCommentsViewController.m in Sources */, 1716AEFC25F2927600CF49EC /* MySiteViewController.swift in Sources */, 7E3E7A6020E44E490075D159 /* FooterContentGroup.swift in Sources */, + 46F584B82624E6380010A723 /* BlockEditorSettings+GutenbergEditorTheme.swift in Sources */, 59A3CADD1CD2FF0C009BFA1B /* BasePageListCell.m in Sources */, 436D56202117312700CEAA33 /* RegisterDomainSuggestionsTableViewController.swift in Sources */, E114D79A153D85A800984182 /* WPError.m in Sources */, @@ -16955,6 +17003,7 @@ FAB8004925AEDC2300D5D54A /* JetpackBackupCompleteViewController.swift in Sources */, 9A8ECE0C2254A3260043C8DA /* JetpackLoginViewController.swift in Sources */, 5DAE40AD19EC70930011A0AE /* ReaderPostHeaderView.m in Sources */, + 46F583A92624CE790010A723 /* BlockEditorSettings+CoreDataClass.swift in Sources */, 4054F4622214F50300D261AB /* StreakStatsRecordValue+CoreDataProperties.swift in Sources */, B50C0C641EF42B3A00372C65 /* Header+WordPress.swift in Sources */, F504D2B125D60C5900A2764C /* StoryMediaLoader.swift in Sources */, @@ -16971,6 +17020,7 @@ F53FF3AA23EA725C001AD596 /* SiteIconView.swift in Sources */, FA3536F525B01A2C0005A3A0 /* JetpackRestoreCompleteViewController.swift in Sources */, 7E7947AD210BAC7B005BB851 /* FormattableNoticonRange.swift in Sources */, + 46F583AB2624CE790010A723 /* BlockEditorSettings+CoreDataProperties.swift in Sources */, B55086211CC15CCB004EADB4 /* PromptViewController.swift in Sources */, 40C403EC2215CD1300E8C894 /* SearchResultsStatsRecordValue+CoreDataProperties.swift in Sources */, 82FC612C1FA8B7FC00A1757E /* ActivityListRow.swift in Sources */, @@ -17028,6 +17078,7 @@ 9A4E939F2268D9B400E14823 /* UIViewController+NoResults.swift in Sources */, E1CB6DA3200F376400945457 /* TimeZoneStore.swift in Sources */, B0B68A9C252FA91E0001B28C /* UserSuggestion+CoreDataClass.swift in Sources */, + 46F583D42624D0BC0010A723 /* Blog+BlockEditorSettings.swift in Sources */, D80BC7A4207487F200614A59 /* MediaLibraryPicker.swift in Sources */, E663D1901C65383E0017F109 /* SharingAccountViewController.swift in Sources */, 93DEB88219E5BF7100F9546D /* TodayExtensionService.m in Sources */, @@ -17183,6 +17234,7 @@ D86572172186C3600023A99C /* WizardDelegate.swift in Sources */, 73713583208EA4B900CCDFC8 /* Blog+Files.swift in Sources */, 439F4F3C219B78B500F8D0C7 /* RevisionDiffsBrowserViewController.swift in Sources */, + 46F583AD2624CE790010A723 /* BlockEditorSettingElement+CoreDataClass.swift in Sources */, FAD256932611B01700EDAF88 /* UIColor+WordPressColors.swift in Sources */, E174F6E6172A73960004F23A /* WPAccount.m in Sources */, FF619DD51C75246900903B65 /* CLPlacemark+Formatting.swift in Sources */, @@ -17271,6 +17323,7 @@ 3250490724F988220036B47F /* Interpolation.swift in Sources */, E17FEAD8221490F7006E1D2D /* PostEditorAnalyticsSession.swift in Sources */, 738B9A5221B85CF20005062B /* SiteCreationWizardLauncher.swift in Sources */, + 46F583AF2624CE790010A723 /* BlockEditorSettingElement+CoreDataProperties.swift in Sources */, 3234B8E7252FA0930068DA40 /* ReaderSitesCardCell.swift in Sources */, D865722E2186F96D0023A99C /* SiteSegmentsWizardContent.swift in Sources */, E66969E01B9E648100EC9C00 /* ReaderTopicToReaderDefaultTopic37to38.swift in Sources */, @@ -17758,6 +17811,7 @@ 73178C2C21BEE09300E37C9A /* SiteCreationHeaderDataTests.swift in Sources */, F9941D1822A805F600788F33 /* UIImage+XCAssetTests.swift in Sources */, 400199AD22259FF300EB0906 /* AnnualAndMostPopularTimeStatsRecordValueTests.swift in Sources */, + 46F584EE2625FF100010A723 /* EditorThemeTests.swift in Sources */, 08A2AD7B1CCED8E500E84454 /* PostCategoryServiceTests.m in Sources */, D88A64A8208D9733008AE9BC /* ThumbnailCollectionTests.swift in Sources */, 572FB401223A806000933C76 /* NoticeStoreTests.swift in Sources */, @@ -17828,6 +17882,7 @@ 40C403EE2215CE9500E8C894 /* SearchResultsStatsRecordValueTests.swift in Sources */, FF7C89A31E3A1029000472A8 /* MediaLibraryPickerDataSourceTests.swift in Sources */, C81CCD86243C00E000A83E27 /* TenorPageableTests.swift in Sources */, + 46F58501262605930010A723 /* BlockEditorSettingsServiceTests.swift in Sources */, 8BBBEBB224B8F8C0005E358E /* ReaderCardTests.swift in Sources */, D81C2F5E20F88CE5002AE1F1 /* MarkAsSpamActionTests.swift in Sources */, D848CC1520FF33FC00A9038F /* NotificationContentRangeTests.swift in Sources */, @@ -18343,7 +18398,6 @@ FABB21E92602FC2C00C8785C /* MenuItemSourceHeaderView.m in Sources */, FABB21EA2602FC2C00C8785C /* RestoreWarningView.swift in Sources */, FABB21EB2602FC2C00C8785C /* GutenbergWebNavigationViewController.swift in Sources */, - FABB21EC2602FC2C00C8785C /* EditorThemeStore.swift in Sources */, FABB21ED2602FC2C00C8785C /* WPTabBarController+ReaderTabNavigation.swift in Sources */, FABB21EE2602FC2C00C8785C /* WPStyleGuide+Blog.swift in Sources */, FABB21EF2602FC2C00C8785C /* KeyboardDismissHelper.swift in Sources */, @@ -18687,6 +18741,7 @@ FABB233A2602FC2C00C8785C /* RevisionsNavigationController.swift in Sources */, FABB233B2602FC2C00C8785C /* AnnualAndMostPopularTimeStatsRecordValue+CoreDataClass.swift in Sources */, FABB233C2602FC2C00C8785C /* MediaProgressCoordinatorNoticeViewModel.swift in Sources */, + 46F584832624DCC80010A723 /* BlockEditorSettingsService.swift in Sources */, FABB233D2602FC2C00C8785C /* SiteStatsPeriodViewModel.swift in Sources */, FABB233E2602FC2C00C8785C /* DomainSuggestionsTableViewController.swift in Sources */, FABB233F2602FC2C00C8785C /* AnnouncementCell.swift in Sources */, @@ -18757,6 +18812,7 @@ FABB237F2602FC2C00C8785C /* SearchAdsAttribution.swift in Sources */, FABB23802602FC2C00C8785C /* ReaderCommentsViewController.m in Sources */, FABB23812602FC2C00C8785C /* MySiteViewController.swift in Sources */, + 46F584B92624E6380010A723 /* BlockEditorSettings+GutenbergEditorTheme.swift in Sources */, FABB23822602FC2C00C8785C /* FooterContentGroup.swift in Sources */, FABB23832602FC2C00C8785C /* BasePageListCell.m in Sources */, FABB23842602FC2C00C8785C /* RegisterDomainSuggestionsTableViewController.swift in Sources */, @@ -19039,6 +19095,7 @@ FABB24972602FC2C00C8785C /* JetpackLoginViewController.swift in Sources */, FABB24982602FC2C00C8785C /* ReaderPostHeaderView.m in Sources */, FABB24992602FC2C00C8785C /* StreakStatsRecordValue+CoreDataProperties.swift in Sources */, + 46F583AA2624CE790010A723 /* BlockEditorSettings+CoreDataClass.swift in Sources */, FABB249A2602FC2C00C8785C /* Header+WordPress.swift in Sources */, FABB249B2602FC2C00C8785C /* StoryMediaLoader.swift in Sources */, FABB249C2602FC2C00C8785C /* EditorSettingsService.swift in Sources */, @@ -19055,6 +19112,7 @@ FABB24A62602FC2C00C8785C /* JetpackRestoreCompleteViewController.swift in Sources */, FABB24A72602FC2C00C8785C /* FormattableNoticonRange.swift in Sources */, FABB24A82602FC2C00C8785C /* PromptViewController.swift in Sources */, + 46F583AC2624CE790010A723 /* BlockEditorSettings+CoreDataProperties.swift in Sources */, FABB24A92602FC2C00C8785C /* SearchResultsStatsRecordValue+CoreDataProperties.swift in Sources */, FABB24AA2602FC2C00C8785C /* ActivityListRow.swift in Sources */, FABB24AB2602FC2C00C8785C /* UIColor+MurielColorsObjC.swift in Sources */, @@ -19112,6 +19170,7 @@ FABB24DF2602FC2C00C8785C /* TimeZoneStore.swift in Sources */, FABB24E02602FC2C00C8785C /* UserSuggestion+CoreDataClass.swift in Sources */, FABB24E12602FC2C00C8785C /* MediaLibraryPicker.swift in Sources */, + 46F583D52624D0BC0010A723 /* Blog+BlockEditorSettings.swift in Sources */, FABB24E22602FC2C00C8785C /* SharingAccountViewController.swift in Sources */, FABB24E32602FC2C00C8785C /* TodayExtensionService.m in Sources */, FABB24E42602FC2C00C8785C /* UIApplication+mainWindow.swift in Sources */, @@ -19267,6 +19326,7 @@ FABB25782602FC2C00C8785C /* WizardDelegate.swift in Sources */, FABB25792602FC2C00C8785C /* Blog+Files.swift in Sources */, FABB257A2602FC2C00C8785C /* RevisionDiffsBrowserViewController.swift in Sources */, + 46F583AE2624CE790010A723 /* BlockEditorSettingElement+CoreDataClass.swift in Sources */, FABB257B2602FC2C00C8785C /* WPAccount.m in Sources */, FABB257C2602FC2C00C8785C /* CLPlacemark+Formatting.swift in Sources */, FABB257D2602FC2C00C8785C /* SiteStatsDetailTableViewController.swift in Sources */, @@ -19355,6 +19415,7 @@ FABB25CF2602FC2C00C8785C /* PostEditorAnalyticsSession.swift in Sources */, FABB25D02602FC2C00C8785C /* SiteCreationWizardLauncher.swift in Sources */, FABB25D12602FC2C00C8785C /* ReaderSitesCardCell.swift in Sources */, + 46F583B02624CE790010A723 /* BlockEditorSettingElement+CoreDataProperties.swift in Sources */, FABB25D22602FC2C00C8785C /* SiteSegmentsWizardContent.swift in Sources */, FABB25D32602FC2C00C8785C /* ReaderTopicToReaderDefaultTopic37to38.swift in Sources */, FABB25D42602FC2C00C8785C /* UploadsManager.swift in Sources */, @@ -23445,11 +23506,11 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ - 24CE2EB0258D687A0000C297 /* WordPressFlux */ = { + 24CE2EB0258D687A0000C297 /* SwiftPackageProductDependency */ = { isa = XCSwiftPackageProductDependency; productName = WordPressFlux; }; - FABB1FA62602FC2C00C8785C /* WordPressFlux */ = { + FABB1FA62602FC2C00C8785C /* SwiftPackageProductDependency */ = { isa = XCSwiftPackageProductDependency; productName = WordPressFlux; }; @@ -23471,6 +23532,7 @@ E125443B12BF5A7200D87A0A /* WordPress.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + 46F583A42624C8FA0010A723 /* WordPress 120.xcdatamodel */, C9D7DDBF2613B84500104E95 /* WordPress 119.xcdatamodel */, 46365555260E1DE5006398E4 /* WordPress 118.xcdatamodel */, C99B039B2602F3CB00CA71EB /* WordPress 117.xcdatamodel */, @@ -23591,7 +23653,7 @@ 8350E15911D28B4A00A7B073 /* WordPress.xcdatamodel */, E125443D12BF5A7200D87A0A /* WordPress 2.xcdatamodel */, ); - currentVersion = C9D7DDBF2613B84500104E95 /* WordPress 119.xcdatamodel */; + currentVersion = 46F583A42624C8FA0010A723 /* WordPress 120.xcdatamodel */; name = WordPress.xcdatamodeld; path = Classes/WordPress.xcdatamodeld; sourceTree = "<group>"; diff --git a/WordPress/WordPressTest/BlockEditorSettingsServiceTests.swift b/WordPress/WordPressTest/BlockEditorSettingsServiceTests.swift new file mode 100644 index 000000000000..8abaa8e183ce --- /dev/null +++ b/WordPress/WordPressTest/BlockEditorSettingsServiceTests.swift @@ -0,0 +1,109 @@ +import XCTest +import Nimble +@testable import WordPress + +class BlockEditorSettingsServiceTests: XCTestCase { + + private let twentytwentyResponseFilename = "get_wp_v2_themes_twentytwenty" + private let twentytwentyoneResponseFilename = "get_wp_v2_themes_twentytwentyone" + + private var service: BlockEditorSettingsService! + private var contextManager: TestContextManager! + private var context: NSManagedObjectContext! + var mockRemoteApi: MockWordPressComRestApi! + private var blog: Blog! + + override func setUp() { + contextManager = TestContextManager() + context = contextManager.newDerivedContext() + mockRemoteApi = MockWordPressComRestApi() + blog = ModelTestHelper.insertDotComBlog(context: context) + blog.dotComID = NSNumber(value: 1) + blog.account?.authToken = "auth" + + service = BlockEditorSettingsService(blog: blog, remoteAPI: mockRemoteApi, context: context) + } + + // MARK: Editor `theme_supports` support + func testThemeSupportsNewTheme() { + let waitExpectation = expectation(description: "Theme should be successfully fetched") + let mockedResponse = mockedData(withFilename: twentytwentyoneResponseFilename) + service.fetchSettings { (hasChanges, result) in + expect(hasChanges).to(beTrue()) + expect(result).toNot(beNil()) + waitExpectation.fulfill() + } + + expect(self.mockRemoteApi.getMethodCalled).to(beTrue()) + mockRemoteApi.successBlockPassedIn!(mockedResponse, HTTPURLResponse()) + + waitForExpectations(timeout: 0.1) + validateResponse() + expect(self.blog.blockEditorSettings?.checksum).toNot(beNil()) + } + + func testThemeSupportsThemeChange() { + setData(withFilename: twentytwentyResponseFilename) + let originalChecksum = blog.blockEditorSettings?.checksum ?? "" + + let waitExpectation = expectation(description: "Theme should be successfully fetched") + let mockedResponse = mockedData(withFilename: twentytwentyoneResponseFilename) + service.fetchSettings { (hasChanges, result) in + expect(hasChanges).to(beTrue()) + expect(result).toNot(beNil()) + waitExpectation.fulfill() + } + + expect(self.mockRemoteApi.getMethodCalled).to(beTrue()) + mockRemoteApi.successBlockPassedIn!(mockedResponse, HTTPURLResponse()) + + waitForExpectations(timeout: 0.1) + validateResponse() + expect(self.blog.blockEditorSettings?.checksum).toNot(equal(originalChecksum)) + } + + func testThemeSupportsThemeIsTheSame() { + setData(withFilename: twentytwentyoneResponseFilename) + let originalChecksum = blog.blockEditorSettings?.checksum ?? "" + + let waitExpectation = expectation(description: "Theme should be successfully fetched") + let mockedResponse = mockedData(withFilename: twentytwentyoneResponseFilename) + service.fetchSettings { (hasChanges, result) in + expect(hasChanges).to(beFalse()) + expect(result).toNot(beNil()) + waitExpectation.fulfill() + } + + expect(self.mockRemoteApi.getMethodCalled).to(beTrue()) + mockRemoteApi.successBlockPassedIn!(mockedResponse, HTTPURLResponse()) + + waitForExpectations(timeout: 0.1) + validateResponse() + expect(self.blog.blockEditorSettings?.checksum).to(equal(originalChecksum)) + } + + private func validateResponse() { + expect(self.mockRemoteApi.URLStringPassedIn!).to(equal("/wp/v2/sites/1/themes?status=active")) + expect(self.blog.blockEditorSettings?.colors?.count).to(beGreaterThan(0)) + expect(self.blog.blockEditorSettings?.gradients?.count).to(beGreaterThan(0)) + } +} + +extension BlockEditorSettingsServiceTests { + + func mockedData(withFilename filename: String) -> AnyObject { + let json = Bundle(for: SiteSegmentTests.self).url(forResource: filename, withExtension: "json")! + let data = try! Data(contentsOf: json) + return try! JSONSerialization.jsonObject(with: data, options: .allowFragments) as AnyObject + } + + func setData(withFilename filename: String) { + let waitExpectation = expectation(description: "Theme should be successfully fetched") + let mockedResponse = mockedData(withFilename: filename) + service.fetchSettings { (hasChanges, result) in + waitExpectation.fulfill() + } + mockRemoteApi.successBlockPassedIn!(mockedResponse, HTTPURLResponse()) + waitForExpectations(timeout: 0.1) + } +} diff --git a/WordPress/WordPressTest/EditorThemeTests.swift b/WordPress/WordPressTest/EditorThemeTests.swift new file mode 100644 index 000000000000..24e2b802d270 --- /dev/null +++ b/WordPress/WordPressTest/EditorThemeTests.swift @@ -0,0 +1,36 @@ +import XCTest +import Nimble +@testable import WordPress + +class EditorThemeTests: XCTestCase { + + func testParseEquivalentObjects() throws { + let testJSON: String = """ + [{"stylesheet":"twentytwentyone","template":"twentytwentyone","textdomain":"twentytwentyone","version":"1.2","theme_supports":{"editor-color-palette":[{"name":"Black","slug":"black","color":"#000000"}],"editor-gradient-presets":[{"name":"Purple to yellow","gradient":"linear-gradient(160deg, #D1D1E4 0%, #EEEADD 100%)","slug":"purple-to-yellow"}]}}] + """ + + /// The data is the same here but the keys are rearranged for dictionary objects. This is used to validate that the order won't effect the checksum. + let testJSONModifiedKeyOrder: String = """ + [{"version":"1.2","stylesheet":"twentytwentyone","textdomain":"twentytwentyone","template":"twentytwentyone","theme_supports":{"editor-gradient-presets":[{"gradient":"linear-gradient(160deg, #D1D1E4 0%, #EEEADD 100%)","name":"Purple to yellow","slug":"purple-to-yellow"}],"editor-color-palette":[{"slug":"black","name":"Black","color":"#000000"}]}}] + """ + + let editorTheme1 = try JSONDecoder().decode([EditorTheme].self, from: testJSON.data(using: .utf8)!).first! + let editorTheme2 = try JSONDecoder().decode([EditorTheme].self, from: testJSONModifiedKeyOrder.data(using: .utf8)!).first! + expect(editorTheme1.checksum).to(equal(editorTheme2.checksum)) + } + + func testParseDifferentObjects() throws { + let testJSON: String = """ + [{"stylesheet":"twentytwentyone","template":"twentytwentyone","textdomain":"twentytwentyone","version":"1.2","theme_supports":{"editor-color-palette":[{"name":"Black","slug":"black","color":"#000000"}],"editor-gradient-presets":[{"name":"Purple to yellow","gradient":"linear-gradient(160deg, #D1D1E4 0%, #EEEADD 100%)","slug":"purple-to-yellow"}]}}] + """ + + /// The data is almost the same here but has a small modification in hopes it will registes a change to the checksum + let testJSONModifiedKeyOrder: String = """ + [{"version":"1.2","stylesheet":"twentytwentyone","textdomain":"twentytwentyone","template":"twentytwentyone","theme_supports":{"editor-gradient-presets":[{"gradient":"linear-gradient(160deg, #D1D1E4 0%, #EEEADD 100%)","name":"Purple to yellowish","slug":"purple-to-yellow"}],"editor-color-palette":[{"slug":"black","name":"Black","color":"#000000"}]}}] + """ + + let editorTheme1 = try JSONDecoder().decode([EditorTheme].self, from: testJSON.data(using: .utf8)!).first! + let editorTheme2 = try JSONDecoder().decode([EditorTheme].self, from: testJSONModifiedKeyOrder.data(using: .utf8)!).first! + expect(editorTheme1.checksum).toNot(equal(editorTheme2.checksum)) + } +} diff --git a/WordPress/WordPressTest/Test Data/get_wp_v2_themes_twentytwenty.json b/WordPress/WordPressTest/Test Data/get_wp_v2_themes_twentytwenty.json new file mode 100644 index 000000000000..a212703ee074 --- /dev/null +++ b/WordPress/WordPressTest/Test Data/get_wp_v2_themes_twentytwenty.json @@ -0,0 +1,164 @@ +[ + { + "stylesheet": "twentytwenty", + "template": "twentytwenty", + "requires_php": "5.2.4", + "requires_wp": "4.7", + "textdomain": "twentytwenty", + "version": "1.7", + "screenshot": "https://unique-peccary.jurassic.ninja/wp-content/themes/twentytwenty/screenshot.png", + "author": { + "raw": "the WordPress team", + "rendered": "<a href=\"https://wordpress.org/\">the WordPress team</a>" + }, + "author_uri": { + "raw": "https://wordpress.org/", + "rendered": "https://wordpress.org/" + }, + "description": { + "raw": "Our default theme for 2020 is designed to take full advantage of the flexibility of the block editor. Organizations and businesses have the ability to create dynamic landing pages with endless layouts using the group and column blocks. The centered content column and fine-tuned typography also makes it perfect for traditional blogs. Complete editor styles give you a good idea of what your content will look like, even before you publish. You can give your site a personal touch by changing the background colors and the accent color in the Customizer. The colors of all elements on your site are automatically calculated based on the colors you pick, ensuring a high, accessible color contrast for your visitors.", + "rendered": "Our default theme for 2020 is designed to take full advantage of the flexibility of the block editor. Organizations and businesses have the ability to create dynamic landing pages with endless layouts using the group and column blocks. The centered content column and fine-tuned typography also makes it perfect for traditional blogs. Complete editor styles give you a good idea of what your content will look like, even before you publish. You can give your site a personal touch by changing the background colors and the accent color in the Customizer. The colors of all elements on your site are automatically calculated based on the colors you pick, ensuring a high, accessible color contrast for your visitors." + }, + "name": { + "raw": "Twenty Twenty", + "rendered": "Twenty Twenty" + }, + "tags": { + "raw": [ + "blog", + "one-column", + "custom-background", + "custom-colors", + "custom-logo", + "custom-menu", + "editor-style", + "featured-images", + "footer-widgets", + "full-width-template", + "rtl-language-support", + "sticky-post", + "theme-options", + "threaded-comments", + "translation-ready", + "block-patterns", + "block-styles", + "wide-blocks", + "accessibility-ready" + ], + "rendered": "blog, one-column, custom-background, custom-colors, custom-logo, custom-menu, editor-style, featured-images, footer-widgets, full-width-template, rtl-language-support, sticky-post, theme-options, threaded-comments, translation-ready, block-patterns, block-styles, wide-blocks, accessibility-ready" + }, + "theme_uri": { + "raw": "https://wordpress.org/themes/twentytwenty/", + "rendered": "https://wordpress.org/themes/twentytwenty/" + }, + "status": "active", + "theme_supports": { + "align-wide": true, + "automatic-feed-links": true, + "custom-background": { + "default-image": "", + "default-preset": "default", + "default-position-x": "left", + "default-position-y": "top", + "default-size": "auto", + "default-repeat": "repeat", + "default-attachment": "scroll", + "default-color": "f5efe0" + }, + "custom-header": false, + "custom-logo": { + "width": 120, + "height": 90, + "flex-width": true, + "flex-height": true, + "header-text": [], + "unlink-homepage-logo": false + }, + "customize-selective-refresh-widgets": true, + "dark-editor-style": false, + "disable-custom-colors": false, + "disable-custom-font-sizes": false, + "disable-custom-gradients": false, + "editor-color-palette": [ + { + "name": "Accent Color", + "slug": "accent", + "color": "#cd2653" + }, + { + "name": "Primary", + "slug": "primary", + "color": "#000000" + }, + { + "name": "Secondary", + "slug": "secondary", + "color": "#6d6d6d" + }, + { + "name": "Subtle Background", + "slug": "subtle-background", + "color": "#dcd7ca" + }, + { + "name": "Background Color", + "slug": "background", + "color": "#f5efe0" + } + ], + "editor-font-sizes": [ + { + "name": "Small", + "size": 18, + "slug": "small" + }, + { + "name": "Regular", + "size": 21, + "slug": "normal" + }, + { + "name": "Large", + "size": 26.25, + "slug": "large" + }, + { + "name": "Larger", + "size": 32, + "slug": "larger" + } + ], + "editor-gradient-presets": false, + "editor-styles": true, + "html5": [ + "search-form", + "comment-form", + "comment-list", + "gallery", + "caption", + "script", + "style", + "navigation-widgets" + ], + "formats": [ + "standard" + ], + "post-thumbnails": true, + "responsive-embeds": true, + "title-tag": true, + "wp-block-styles": false + }, + "_links": { + "self": [ + { + "href": "https://unique-peccary.jurassic.ninja/wp-json/wp/v2/themes/twentytwenty" + } + ], + "collection": [ + { + "href": "https://unique-peccary.jurassic.ninja/wp-json/wp/v2/themes" + } + ] + } + } +] diff --git a/WordPress/WordPressTest/Test Data/get_wp_v2_themes_twentytwentyone.json b/WordPress/WordPressTest/Test Data/get_wp_v2_themes_twentytwentyone.json new file mode 100644 index 000000000000..5666dad75d5a --- /dev/null +++ b/WordPress/WordPressTest/Test Data/get_wp_v2_themes_twentytwentyone.json @@ -0,0 +1,247 @@ +[ + { + "stylesheet": "twentytwentyone", + "template": "twentytwentyone", + "requires_php": "5.6", + "requires_wp": "5.3", + "textdomain": "twentytwentyone", + "version": "1.2", + "screenshot": "https://unique-peccary.jurassic.ninja/wp-content/themes/twentytwentyone/screenshot.png", + "author": { + "raw": "the WordPress team", + "rendered": "<a href=\"https://wordpress.org/\">the WordPress team</a>" + }, + "author_uri": { + "raw": "https://wordpress.org/", + "rendered": "https://wordpress.org/" + }, + "description": { + "raw": "Twenty Twenty-One is a blank canvas for your ideas and it makes the block editor your best brush. With new block patterns, which allow you to create a beautiful layout in a matter of seconds, this theme’s soft colors and eye-catching — yet timeless — design will let your work shine. Take it for a spin! See how Twenty Twenty-One elevates your portfolio, business website, or personal blog.", + "rendered": "Twenty Twenty-One is a blank canvas for your ideas and it makes the block editor your best brush. With new block patterns, which allow you to create a beautiful layout in a matter of seconds, this theme’s soft colors and eye-catching — yet timeless — design will let your work shine. Take it for a spin! See how Twenty Twenty-One elevates your portfolio, business website, or personal blog." + }, + "name": { + "raw": "Twenty Twenty-One", + "rendered": "Twenty Twenty-One" + }, + "tags": { + "raw": [ + "one-column", + "accessibility-ready", + "custom-colors", + "custom-menu", + "custom-logo", + "editor-style", + "featured-images", + "footer-widgets", + "block-patterns", + "rtl-language-support", + "sticky-post", + "threaded-comments", + "translation-ready" + ], + "rendered": "one-column, accessibility-ready, custom-colors, custom-menu, custom-logo, editor-style, featured-images, footer-widgets, block-patterns, rtl-language-support, sticky-post, threaded-comments, translation-ready" + }, + "theme_uri": { + "raw": "https://wordpress.org/themes/twentytwentyone/", + "rendered": "https://wordpress.org/themes/twentytwentyone/" + }, + "status": "active", + "theme_supports": { + "align-wide": true, + "automatic-feed-links": true, + "custom-background": { + "default-image": "", + "default-preset": "default", + "default-position-x": "left", + "default-position-y": "top", + "default-size": "auto", + "default-repeat": "repeat", + "default-attachment": "scroll", + "default-color": "d1e4dd" + }, + "custom-header": false, + "custom-logo": { + "width": 300, + "height": 100, + "flex-width": true, + "flex-height": true, + "header-text": [], + "unlink-homepage-logo": true + }, + "customize-selective-refresh-widgets": true, + "dark-editor-style": false, + "disable-custom-colors": false, + "disable-custom-font-sizes": false, + "disable-custom-gradients": false, + "editor-color-palette": [ + { + "name": "Black", + "slug": "black", + "color": "#000000" + }, + { + "name": "Dark gray", + "slug": "dark-gray", + "color": "#28303D" + }, + { + "name": "Gray", + "slug": "gray", + "color": "#39414D" + }, + { + "name": "Green", + "slug": "green", + "color": "#D1E4DD" + }, + { + "name": "Blue", + "slug": "blue", + "color": "#D1DFE4" + }, + { + "name": "Purple", + "slug": "purple", + "color": "#D1D1E4" + }, + { + "name": "Red", + "slug": "red", + "color": "#E4D1D1" + }, + { + "name": "Orange", + "slug": "orange", + "color": "#E4DAD1" + }, + { + "name": "Yellow", + "slug": "yellow", + "color": "#EEEADD" + }, + { + "name": "White", + "slug": "white", + "color": "#FFFFFF" + } + ], + "editor-font-sizes": [ + { + "name": "Extra small", + "size": 16, + "slug": "extra-small" + }, + { + "name": "Small", + "size": 18, + "slug": "small" + }, + { + "name": "Normal", + "size": 20, + "slug": "normal" + }, + { + "name": "Large", + "size": 24, + "slug": "large" + }, + { + "name": "Extra large", + "size": 40, + "slug": "extra-large" + }, + { + "name": "Huge", + "size": 96, + "slug": "huge" + }, + { + "name": "Gigantic", + "size": 144, + "slug": "gigantic" + } + ], + "editor-gradient-presets": [ + { + "name": "Purple to yellow", + "gradient": "linear-gradient(160deg, #D1D1E4 0%, #EEEADD 100%)", + "slug": "purple-to-yellow" + }, + { + "name": "Yellow to purple", + "gradient": "linear-gradient(160deg, #EEEADD 0%, #D1D1E4 100%)", + "slug": "yellow-to-purple" + }, + { + "name": "Green to yellow", + "gradient": "linear-gradient(160deg, #D1E4DD 0%, #EEEADD 100%)", + "slug": "green-to-yellow" + }, + { + "name": "Yellow to green", + "gradient": "linear-gradient(160deg, #EEEADD 0%, #D1E4DD 100%)", + "slug": "yellow-to-green" + }, + { + "name": "Red to yellow", + "gradient": "linear-gradient(160deg, #E4D1D1 0%, #EEEADD 100%)", + "slug": "red-to-yellow" + }, + { + "name": "Yellow to red", + "gradient": "linear-gradient(160deg, #EEEADD 0%, #E4D1D1 100%)", + "slug": "yellow-to-red" + }, + { + "name": "Purple to red", + "gradient": "linear-gradient(160deg, #D1D1E4 0%, #E4D1D1 100%)", + "slug": "purple-to-red" + }, + { + "name": "Red to purple", + "gradient": "linear-gradient(160deg, #E4D1D1 0%, #D1D1E4 100%)", + "slug": "red-to-purple" + } + ], + "editor-styles": true, + "html5": [ + "comment-form", + "comment-list", + "gallery", + "caption", + "style", + "script", + "navigation-widgets" + ], + "formats": [ + "standard", + "link", + "aside", + "gallery", + "image", + "quote", + "status", + "video", + "audio", + "chat" + ], + "post-thumbnails": true, + "responsive-embeds": true, + "title-tag": true, + "wp-block-styles": true + }, + "_links": { + "self": [ + { + "href": "https://unique-peccary.jurassic.ninja/wp-json/wp/v2/themes/twentytwentyone" + } + ], + "collection": [ + { + "href": "https://unique-peccary.jurassic.ninja/wp-json/wp/v2/themes" + } + ] + } + } +]