From 3e0f6997c53e6bea11681ac8926d9483a830b0a1 Mon Sep 17 00:00:00 2001 From: Mojmir Fendek Date: Fri, 12 Nov 2021 08:07:50 +1300 Subject: [PATCH 01/24] NEW: Empty link title fallbacks to Page. (#37) * NEW: Empty link title fallbacks to Page. * PR fixes. --- src/Models/SiteTreeLink.php | 45 +++++++++++++++++++++++++++++++++++-- tests/php/LinkModelTest.php | 29 ++++++++++++++++++++++++ tests/php/LinkModelTest.yml | 8 +++++++ 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/Models/SiteTreeLink.php b/src/Models/SiteTreeLink.php index a8ffb65d..fb827b54 100644 --- a/src/Models/SiteTreeLink.php +++ b/src/Models/SiteTreeLink.php @@ -7,7 +7,8 @@ use SilverStripe\Forms\TreeDropdownField; /** - * A link to a Page in the CMS. + * A link to a Page in the CMS + * * @property SiteTree $Page * @property int $PageID * @property string $Anchor @@ -30,6 +31,7 @@ public function generateLinkDescription(array $data): string return ''; } + /** @var SiteTree $page */ $page = SiteTree::get()->byID($data['PageID']); return $page ? $page->URLSegment : ''; @@ -58,12 +60,51 @@ public function getCMSFields() return $fields; } - public function getURL() + public function getURL(): ?string { $url = $this->Page ? $this->Page->Link() : ''; + if ($this->Anchor) { $url .= '#' . $this->Anchor; } + return $url; } + + public function onBeforeWrite(): void + { + parent::onBeforeWrite(); + + $this->populateTitle(); + } + + protected function populateTitle(): void + { + $title = $this->getTitleFromPage(); + $this->extend('updateGetTitleFromPage', $title); + $this->Title = $title; + } + + /** + * Try to populate link title from page title in case we don't have a title yet + * + * @return string|null + */ + protected function getTitleFromPage(): ?string + { + if ($this->Title) { + // If we already have a title, we can just bail out without any changes + return $this->Title; + } + + $page = $this->Page; + + if (!$page || !$page->exists()) { + // We don't have a page to fall back to + return null; + } + + // Use page title as a default value in case CMS user didn't provide the title + return $this->Page->Title; + } } diff --git a/tests/php/LinkModelTest.php b/tests/php/LinkModelTest.php index 4232a1ec..bb475f15 100644 --- a/tests/php/LinkModelTest.php +++ b/tests/php/LinkModelTest.php @@ -2,8 +2,11 @@ namespace SilverStripe\Link\Tests; +use SilverStripe\CMS\Model\SiteTree; use SilverStripe\Dev\SapphireTest; use SilverStripe\Link\Models\Link; +use SilverStripe\Link\Models\SiteTreeLink; +use SilverStripe\ORM\ValidationException; class LinkModelTest extends SapphireTest { @@ -18,4 +21,30 @@ public function testLinkModel(): void $this->assertEquals('FormBuilderModal', $model->LinkTypeHandlerName()); } + + /** + * @throws ValidationException + */ + public function testSiteTreeLinkTitleFallback(): void + { + /** @var SiteTreeLink $model */ + $model = $this->objFromFixture(SiteTreeLink::class, 'page-link-1'); + + $this->assertEquals('PageLink1', $model->Title, 'We expect to get a default Link title'); + + /** @var SiteTree $page */ + $page = $this->objFromFixture(SiteTree::class, 'page-1'); + + $model->PageID = $page->ID; + $model->Title = ''; + $model->write(); + + $this->assertEquals($page->Title, $model->Title, 'We expect to get the linked Page title'); + + $customTitle = 'My custom title'; + $model->Title = $customTitle; + $model->write(); + + $this->assertEquals($customTitle, $model->Title, 'We expect to get the custom title not page title'); + } } diff --git a/tests/php/LinkModelTest.yml b/tests/php/LinkModelTest.yml index 631a5bfa..c0dd5c46 100644 --- a/tests/php/LinkModelTest.yml +++ b/tests/php/LinkModelTest.yml @@ -1,3 +1,11 @@ SilverStripe\Link\Models\Link: link-1: Title: Link1 + +SilverStripe\Link\Models\SiteTreeLink: + page-link-1: + Title: PageLink1 + +SilverStripe\CMS\Model\SiteTree: + page-1: + Title: Page1 From 1068b7830187a1fd9eed89a112a55bdd1e4bbd7f Mon Sep 17 00:00:00 2001 From: Chris Penny Date: Tue, 16 Nov 2021 14:39:42 +1300 Subject: [PATCH 02/24] Update namespace. Add table_name config to all models (#39) * Add table_name config to all models * Update namespace and table names * Update template location --- README.md | 6 +++--- _config/config.yml | 8 ++++---- _config/graphql.yml | 8 ++++---- _config/types.yml | 10 +++++----- src/Extensions/AjaxField.php | 2 +- src/Extensions/LeftAndMain.php | 4 ++-- src/Extensions/ModalController.php | 6 +++--- src/Form/FormFactory.php | 4 ++-- src/Form/JsonField.php | 2 +- src/Form/LinkField.php | 2 +- src/GraphQL/LinkDescriptionQuery.php | 4 ++-- src/GraphQL/LinkDescriptionType.php | 2 +- src/GraphQL/LinkTypeQuery.php | 6 +++--- src/GraphQL/LinkTypeType.php | 2 +- src/JsonData.php | 2 +- src/Models/EmailLink.php | 4 +++- src/Models/ExternalLink.php | 5 +++-- src/Models/FileLink.php | 8 +++++--- src/Models/Link.php | 10 ++++++---- src/Models/SiteTreeLink.php | 5 +++-- src/ORM/DBJson.php | 2 +- src/ORM/DBLink.php | 6 +++--- src/Type/Registry.php | 2 +- src/Type/Type.php | 4 ++-- .../SilverStripe/{Link => LinkField}/Form/JsonField.ss | 0 .../SilverStripe/{Link => LinkField}/Models/Link.ss | 0 tests/php/{LinkModelTest.php => Models/LinkTest.php} | 10 +++++----- tests/php/{LinkModelTest.yml => Models/LinkTest.yml} | 4 ++-- 28 files changed, 68 insertions(+), 60 deletions(-) rename templates/SilverStripe/{Link => LinkField}/Form/JsonField.ss (100%) rename templates/SilverStripe/{Link => LinkField}/Models/Link.ss (100%) rename tests/php/{LinkModelTest.php => Models/LinkTest.php} (84%) rename tests/php/{LinkModelTest.yml => Models/LinkTest.yml} (60%) diff --git a/README.md b/README.md index 5d822803..9e27aebc 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,9 @@ You may need to add the repository URL into your `composer.json` via the `reposi ```php 'Varchar(255)' ]; diff --git a/src/Models/ExternalLink.php b/src/Models/ExternalLink.php index 8d49ae20..be9b778b 100644 --- a/src/Models/ExternalLink.php +++ b/src/Models/ExternalLink.php @@ -1,6 +1,6 @@ 'Varchar' ]; - public function generateLinkDescription(array $data): string { return isset($data['ExternalUrl']) ? $data['ExternalUrl'] : ''; diff --git a/src/Models/FileLink.php b/src/Models/FileLink.php index e98cf732..f4e741ee 100644 --- a/src/Models/FileLink.php +++ b/src/Models/FileLink.php @@ -1,24 +1,26 @@ File::class ]; - public function generateLinkDescription(array $data): string { if (empty($data['FileID'])) { diff --git a/src/Models/Link.php b/src/Models/Link.php index 0ae1fd6c..16c4020d 100644 --- a/src/Models/Link.php +++ b/src/Models/Link.php @@ -1,13 +1,13 @@ 'Varchar', 'OpenInNew' => 'Boolean' diff --git a/src/Models/SiteTreeLink.php b/src/Models/SiteTreeLink.php index fb827b54..3aa278e5 100644 --- a/src/Models/SiteTreeLink.php +++ b/src/Models/SiteTreeLink.php @@ -1,6 +1,6 @@ 'Varchar' ]; @@ -24,7 +26,6 @@ class SiteTreeLink extends Link 'Page' => SiteTree::class ]; - public function generateLinkDescription(array $data): string { if (empty($data['PageID'])) { diff --git a/src/ORM/DBJson.php b/src/ORM/DBJson.php index b9c889e0..17ed7325 100644 --- a/src/ORM/DBJson.php +++ b/src/ORM/DBJson.php @@ -1,6 +1,6 @@ Date: Fri, 22 Oct 2021 12:13:24 +1300 Subject: [PATCH 03/24] BUG: Change link type in via a GridField edit form. --- src/Models/EmailLink.php | 3 +- src/Models/Link.php | 106 ++++++++++++++++++++++++++++++++++ src/Models/SiteTreeLink.php | 3 +- tests/php/Models/LinkTest.php | 33 +++++++++++ 4 files changed, 143 insertions(+), 2 deletions(-) diff --git a/src/Models/EmailLink.php b/src/Models/EmailLink.php index d5fd21a8..e0228f32 100644 --- a/src/Models/EmailLink.php +++ b/src/Models/EmailLink.php @@ -3,6 +3,7 @@ namespace SilverStripe\LinkField\Models; use SilverStripe\Forms\EmailField; +use SilverStripe\Forms\FieldList; /** * A link to an Email address. @@ -24,7 +25,7 @@ public function generateLinkDescription(array $data): string return isset($data['Email']) ? $data['Email'] : ''; } - public function getCMSFields() + public function getCMSFields(): FieldList { $fields = parent::getCMSFields(); diff --git a/src/Models/Link.php b/src/Models/Link.php index 16c4020d..3e4b6e6a 100644 --- a/src/Models/Link.php +++ b/src/Models/Link.php @@ -3,8 +3,14 @@ namespace SilverStripe\LinkField\Models; use InvalidArgumentException; +use ReflectionException; +use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Injector\Injector; +use SilverStripe\Forms\CompositeValidator; +use SilverStripe\Forms\DropdownField; use SilverStripe\Forms\FieldList; +use SilverStripe\Forms\Form; +use SilverStripe\Forms\RequiredFields; use SilverStripe\LinkField\JsonData; use SilverStripe\LinkField\Type\Registry; use SilverStripe\LinkField\Type\Type; @@ -28,6 +34,15 @@ class Link extends DataObject implements JsonData, Type 'OpenInNew' => 'Boolean' ]; + /** + * In-memory only property used to change link type + * This case is relevant for CMS edit form which doesn't use React driven UI + * This is a workaround as changing the ClassName directly is not fully supported in the GridField admin + * + * @var string + */ + private $linkType; + public function defineLinkTypeRequirements() { @@ -51,11 +66,80 @@ public function LinkTypeTile(): string return $this->i18n_singular_name(); } + /** + * @param array $data + * @return FieldList + * @throws ReflectionException + */ public function scaffoldLinkFields(array $data): FieldList { return $this->getCMSFields(); } + /** + * @return FieldList + * @throws ReflectionException + */ + public function getCMSFields(): FieldList + { + $fields = parent::getCMSFields(); + $linkTypes = $this->getLinkTypes(); + + if (static::class === self::class) { + // Add a link type selection field for generic links + $fields->addFieldsToTab( + 'Root.Main', + [ + $linkTypeField = DropdownField::create('LinkType', 'Link Type', $linkTypes), + ], + 'Title' + ); + + $linkTypeField->setEmptyString('-- select type --'); + } + + return $fields; + } + + /** + * @return CompositeValidator + */ + public function getCMSCompositeValidator(): CompositeValidator + { + $validator = parent::getCMSCompositeValidator(); + + if (static::class === self::class) { + // Make Link type mandatory for generic links + $validator->addValidator(RequiredFields::create([ + 'LinkType', + ])); + } + + return $validator; + } + + /** + * Form hook defined in @see Form::saveInto() + * We use this to work with an in-memory only field + * + * @param $value + */ + public function saveLinkType($value) + { + $this->linkType = $value; + } + + public function onBeforeWrite(): void + { + // Detect link type change and update the class accordingly + if ($this->linkType && DataObject::singleton($this->linkType) instanceof Link) { + $this->setClassName($this->linkType); + $this->populateDefaults(); + $this->forceChange(); + } + + parent::onBeforeWrite(); + } function setData($data): JsonData { @@ -142,4 +226,26 @@ public function forTemplate() { return $this->renderWith([self::class]); } + + /** + * Get all link types except the generic one + * + * @return array + * @throws ReflectionException + */ + private function getLinkTypes(): array + { + $classes = ClassInfo::subclassesFor(self::class); + $types = []; + + foreach ($classes as $class) { + if ($class === self::class) { + continue; + } + + $types[$class] = ClassInfo::shortName($class); + } + + return $types; + } } diff --git a/src/Models/SiteTreeLink.php b/src/Models/SiteTreeLink.php index 3aa278e5..e4e59660 100644 --- a/src/Models/SiteTreeLink.php +++ b/src/Models/SiteTreeLink.php @@ -4,6 +4,7 @@ use SilverStripe\CMS\Forms\AnchorSelectorField; use SilverStripe\CMS\Model\SiteTree; +use SilverStripe\Forms\FieldList; use SilverStripe\Forms\TreeDropdownField; /** @@ -38,7 +39,7 @@ public function generateLinkDescription(array $data): string return $page ? $page->URLSegment : ''; } - public function getCMSFields() + public function getCMSFields(): FieldList { $fields = parent::getCMSFields(); diff --git a/tests/php/Models/LinkTest.php b/tests/php/Models/LinkTest.php index 90aaabac..e0887792 100644 --- a/tests/php/Models/LinkTest.php +++ b/tests/php/Models/LinkTest.php @@ -2,10 +2,15 @@ namespace SilverStripe\LinkField\Tests\Models; +use ReflectionException; use SilverStripe\CMS\Model\SiteTree; use SilverStripe\Dev\SapphireTest; +use SilverStripe\LinkField\Models\EmailLink; +use SilverStripe\LinkField\Models\ExternalLink; +use SilverStripe\LinkField\Models\FileLink; use SilverStripe\LinkField\Models\Link; use SilverStripe\LinkField\Models\SiteTreeLink; +use SilverStripe\ORM\DataObject; use SilverStripe\ORM\ValidationException; class LinkTest extends SapphireTest @@ -47,4 +52,32 @@ public function testSiteTreeLinkTitleFallback(): void $this->assertEquals($customTitle, $model->Title, 'We expect to get the custom title not page title'); } + + /** + * @param string $class + * @param bool $expected + * @throws ReflectionException + * @dataProvider linkTypeProvider + */ + public function testLinkType(string $class, bool $expected): void + { + /** @var Link $model */ + $model = DataObject::singleton($class); + $fields = $model->getCMSFields(); + $linkTypeField = $fields->fieldByName('Root.Main.LinkType'); + $expected + ? $this->assertNotNull($linkTypeField, 'We expect to a find link type field') + : $this->assertNull($linkTypeField, 'We do not expect to a find link type field'); + } + + public function linkTypeProvider(): array + { + return [ + [EmailLink::class, false], + [ExternalLink::class, false], + [FileLink::class, false], + [SiteTreeLink::class, false], + [Link::class, true], + ]; + } } From 523970490db436bc7948bf67257c1f39a425ff93 Mon Sep 17 00:00:00 2001 From: Mojmir Fendek Date: Fri, 13 May 2022 11:34:11 +1200 Subject: [PATCH 04/24] BUG: Fix Composer autoload settings. --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 28a68da6..b64f4937 100644 --- a/composer.json +++ b/composer.json @@ -36,8 +36,8 @@ }, "autoload": { "psr-4": { - "SilverStripe\\Link\\": "src/", - "SilverStripe\\Link\\Tests\\": "tests/php/" + "SilverStripe\\LinkField\\": "src/", + "SilverStripe\\LinkField\\Tests\\": "tests/php/" } }, "config": { From 1e9c0ec5428d626622392165fd39a81c2550127e Mon Sep 17 00:00:00 2001 From: Mojmir Fendek Date: Fri, 13 May 2022 13:42:11 +1200 Subject: [PATCH 05/24] BUG: Bump PHP versions. --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f7e083cc..b833509b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,14 +5,14 @@ import: jobs: include: - - php: 7.3 + - php: 7.4 env: - DB=MYSQL - REQUIRE_INSTALLER="$REQUIRE_RECIPE 4.x-dev" - PHPUNIT_TEST=1 - PHPCS_TEST=1 - PHPSTAN_TEST=1 - - php: 7.3 + - php: 7.4 env: - DB=PGSQL - REQUIRE_INSTALLER="$REQUIRE_RECIPE 4.x-dev" @@ -24,13 +24,13 @@ jobs: - PDO=1 - REQUIRE_INSTALLER="$REQUIRE_RECIPE 4.x-dev" - PHPUNIT_COVERAGE_TEST=1 - - php: 7.4 + - php: 8.0 env: - DB=MYSQL - REQUIRE_INSTALLER="$REQUIRE_RECIPE 4.x-dev" - PHPUNIT_TEST=1 - REQUIRE_GRAPHQL="^3@dev" - - php: 8.0 + - php: 8.1.0 env: - DB=MYSQL - REQUIRE_INSTALLER="$REQUIRE_RECIPE 4.x-dev" From c1f42f28efaf91065886c6e4f9dd8a37bc44d2e3 Mon Sep 17 00:00:00 2001 From: Mojmir Fendek Date: Fri, 13 May 2022 15:06:19 +1200 Subject: [PATCH 06/24] Debug. --- .travis.yml | 1 - composer.json | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b833509b..683d4b13 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,4 +36,3 @@ jobs: - REQUIRE_INSTALLER="$REQUIRE_RECIPE 4.x-dev" - PHPUNIT_TEST=1 - REQUIRE_GRAPHQL="^3@dev" - - COMPOSER_INSTALL_ARG=--ignore-platform-reqs diff --git a/composer.json b/composer.json index b64f4937..242f8429 100644 --- a/composer.json +++ b/composer.json @@ -10,8 +10,7 @@ "silverstripe/vendor-plugin": "^1" }, "require-dev": { - "sminnee/phpunit": "^5.7", - "squizlabs/php_codesniffer": "^3" + "silverstripe/recipe-testing": "^1 || ^2" }, "license": "BSD-3-Clause", "authors": [ From 636c6792ebf8c266e802814956eb1a8a954f92a7 Mon Sep 17 00:00:00 2001 From: Chris Penny Date: Thu, 30 Jun 2022 07:29:52 +1200 Subject: [PATCH 07/24] PHP 8.1: Suppress deprecation notice --- src/Extensions/ModalController.php | 4 ++-- src/Models/Link.php | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Extensions/ModalController.php b/src/Extensions/ModalController.php index b91e9c03..a22c0149 100644 --- a/src/Extensions/ModalController.php +++ b/src/Extensions/ModalController.php @@ -22,9 +22,9 @@ class ModalController extends Extension 'editorAnchorLink/$ItemID' => 'editorAnchorLink', // Matches LeftAndMain::methodSchema args ]; - private static $allowed_actions = array( + private static $allowed_actions = [ 'DynamicLink', - ); + ]; /** * Builds and returns the external link form diff --git a/src/Models/Link.php b/src/Models/Link.php index 3e4b6e6a..45482c21 100644 --- a/src/Models/Link.php +++ b/src/Models/Link.php @@ -187,6 +187,7 @@ function setData($data): JsonData return $jsonData; } + #[\ReturnTypeWillChange] public function jsonSerialize() { $typeKey = Registry::singleton()->keyByClassName(static::class); From 8f1a258f7f34a908936262e489cdf4125cf27db7 Mon Sep 17 00:00:00 2001 From: Chris Penny Date: Fri, 28 Oct 2022 08:25:23 +1300 Subject: [PATCH 08/24] Add PhoneField. Add migration helpers from Linkable (#48) * Add PhoneField. Add migration helpers from Linkable Co-authored-by: Chris Penny Co-authored-by: Mojmir Fendek --- README.md | 9 + _config/types.yml | 3 + docs/en/linkable-migration.md | 115 +++++++ src/Models/EmailLink.php | 2 - src/Models/ExternalLink.php | 1 - src/Models/FileLink.php | 1 - src/Models/Link.php | 2 - src/Models/PhoneLink.php | 30 ++ src/Models/SiteTreeLink.php | 1 - src/Tasks/LinkableMigrationTask.php | 469 ++++++++++++++++++++++++++++ tests/php/Models/LinkTest.php | 2 + 11 files changed, 628 insertions(+), 7 deletions(-) create mode 100644 docs/en/linkable-migration.md create mode 100644 src/Models/PhoneLink.php create mode 100644 src/Tasks/LinkableMigrationTask.php diff --git a/README.md b/README.md index 9e27aebc..9d766f8a 100644 --- a/README.md +++ b/README.md @@ -57,3 +57,12 @@ class Page extends SiteTree } } ``` + +## Migrating from Shae Dawson's Linkable module + +https://github.com/sheadawson/silverstripe-linkable + +Shae Dawson's Linkable module was a much loved, and much used module. It is, unfortunately, no longer maintained. We +have provided some steps and tasks that we hope can be used to migrate your project from Linkable to LinkField. + +* [Migraiton docs](docs/en/linkable-migration.md) diff --git a/_config/types.yml b/_config/types.yml index f8941cbd..29b5dcbb 100644 --- a/_config/types.yml +++ b/_config/types.yml @@ -16,3 +16,6 @@ SilverStripe\LinkField\Type\Registry: email: classname: SilverStripe\LinkField\Models\EmailLink enabled: true + phone: + classname: SilverStripe\LinkField\Models\PhoneLink + enabled: true diff --git a/docs/en/linkable-migration.md b/docs/en/linkable-migration.md new file mode 100644 index 00000000..bf98b892 --- /dev/null +++ b/docs/en/linkable-migration.md @@ -0,0 +1,115 @@ +# Instructions + +## Preamble + +This migration process covers shifting data from the `Linkable` tables to the appropriate `LinkField` tables. + +This does not cover usages of `EmbeddedObject` (at least, not at this time). + +**Versioned:** If you have `Versioned` `Linkable`, then the expectation is that you will also `Version` `LinkField`. If +you have not `Versioned` `Linkable`, then the expectation is that you will **not** `Version` `LinkField`. + +## Install Silvesrtripe Linkfield + +Install the Silverstripe Linkfield module: + +```bash +$ composer require silverstripe/linkfield 1.x-dev +``` + +Or if you would like the (experimental) GraphQL 4 version: + +```bash +$ composer require silverstripe/linkfield 2.x-dev +``` + +Optionally, you can also remove the Linkable module (though, you might find it useful to keep around as a reference +while you are upgrading your code). + +Do this step at whatever point makes sense to you. + +```bash +$ composer remove sheadawson/silverstripe-linkable +``` + +## Replace app usages + +You should review how you are using the original `Link` model and `LinkField`, but if you don't have any customisations, +then replacing the old with the new **might** be quite simple. + +If you have used imports (`use` statements), then your first step might just be to search for `use [old];` and replace +with `use [new];` (since the class name references have not changed at all). + +Old: `Sheadawson\Linkable\Models\Link` +New: `SilverStripe\LinkField\Models\Link` + +Old: `Sheadawson\Linkable\Forms\LinkField` +New: `SilverStripe\LinkField\Form\LinkField` + +If you have extensions, new fields, etc, then your replacements might need to be a bit more considered. + +The other key (less easy to automate) thing that you'll need to update is that the old `LinkField` required you to +specify the related field with `ID` appended, whereas the new `LinkField` requires you to specify the field without +`ID` appended. EG. + +Old: `LinkField::create('MyLinkID')` +New: `LinkField::create('MyLink')` + +Search for instances of `LinkField::create` and `new LinkField`, and hopefully that should give you all of the places +where you need to update field name references. + +### Configuration + +Be sure to check how the old module classes are referenced in config `yml` files (eg: `app/_config`). Update +appropriately. + +### Populate module + +If you use the populate module, you will not be able to simply "replace" the namespace. Fixture definitions for the +new Linkfield module are quite different. There are entirely different models for different link types, whereas before +it was just a DB field to specify the type. + +## Replace template usages + +Before: You might have had references to `$LinkURL` or `$Link.LinkURL`. +After: These would need to be updated to `$URL` or `$Link.URL` respectively. + +Before: `$OpenInNewWindow` or `$Link.OpenInNewWindow`. +After: `$OpenInNew` or `$Link.OpenInew` respectively. + +Before: `$Link.TargetAttr` or `$TargetAttr` would output the appropriate `target="xx"`. +After: There is no direct replacement. + +This is an area where you should spend some decent effort to make sure each implementation is outputting as you expect +it to. There may be more "handy" methods that Linkable provided that no longer exist (that we haven't covered above). + +## Table structures + +It's important to understand that we are going from a single table in Linkable to multiple tables in LinkField. + +**Before:** We had 1 table with all data, and one of the field in there specified the type of the Link. +**Now:** We have 1 table for each type of Link, with a base `Link` table for all record. + +## Specify any custom configuration + +Have a look at `LinkableMigrationTask`. There are some configuration properties defined in there: + +- `$link_mapping` +- `$email_mapping` +- `$external_mapping` +- `$file_mapping` +- `$phone_mapping` +- `$sitetree_mapping` + +Each of these specifies how an original field from the `LinkableLink` table will map to one of the new LinkField tables. + +If you previously had some custom fields that needed to be available across **all** Link types, then you're (probably) +going to add this as an extension on the (base) `Link` class. This is going to mean that the new fields will be added +to the `LinkField_Link` table. This means that you need to update the configuration for `$link_mapping` so that we +correctly migrate those field values into the `LinkField_Link` table. + +If you had/have a field that you only want displayed on (say) SiteTree links, then you would want to add that extension +to `SiteTreeLink`. This would create new fields in the `LinkField_SiteTreeLink` table, which will mean you need to +also update the config for `$sitetree_mapping`. + +It's important that you get the correct mappings to the correct tables. diff --git a/src/Models/EmailLink.php b/src/Models/EmailLink.php index e0228f32..de01aaa1 100644 --- a/src/Models/EmailLink.php +++ b/src/Models/EmailLink.php @@ -12,14 +12,12 @@ */ class EmailLink extends Link { - private static $table_name = 'LinkField_EmailLink'; private static $db = [ 'Email' => 'Varchar(255)' ]; - public function generateLinkDescription(array $data): string { return isset($data['Email']) ? $data['Email'] : ''; diff --git a/src/Models/ExternalLink.php b/src/Models/ExternalLink.php index be9b778b..d9636cc4 100644 --- a/src/Models/ExternalLink.php +++ b/src/Models/ExternalLink.php @@ -9,7 +9,6 @@ */ class ExternalLink extends Link { - private static $table_name = 'LinkField_ExternalLink'; private static $db = [ diff --git a/src/Models/FileLink.php b/src/Models/FileLink.php index f4e741ee..3571eb9a 100644 --- a/src/Models/FileLink.php +++ b/src/Models/FileLink.php @@ -14,7 +14,6 @@ */ class FileLink extends Link { - private static $table_name = 'LinkField_FileLink'; private static $has_one = [ diff --git a/src/Models/Link.php b/src/Models/Link.php index 45482c21..84a7dc17 100644 --- a/src/Models/Link.php +++ b/src/Models/Link.php @@ -26,7 +26,6 @@ */ class Link extends DataObject implements JsonData, Type { - private static $table_name = 'LinkField_Link'; private static $db = [ @@ -43,7 +42,6 @@ class Link extends DataObject implements JsonData, Type */ private $linkType; - public function defineLinkTypeRequirements() { Requirements::add_i18n_javascript('silverstripe/linkfield:client/lang', false, true); diff --git a/src/Models/PhoneLink.php b/src/Models/PhoneLink.php new file mode 100644 index 00000000..ff851884 --- /dev/null +++ b/src/Models/PhoneLink.php @@ -0,0 +1,30 @@ + 'Varchar(255)' + ]; + + public function generateLinkDescription(array $data): string + { + return isset($data['Phone']) ? $data['Phone'] : ''; + } + + public function getURL() + { + return $this->Phone ? sprintf('tel:%s', $this->Phone) : ''; + } +} diff --git a/src/Models/SiteTreeLink.php b/src/Models/SiteTreeLink.php index e4e59660..0ebccaaa 100644 --- a/src/Models/SiteTreeLink.php +++ b/src/Models/SiteTreeLink.php @@ -16,7 +16,6 @@ */ class SiteTreeLink extends Link { - private static $table_name = 'LinkField_SiteTreeLink'; private static $db = [ diff --git a/src/Tasks/LinkableMigrationTask.php b/src/Tasks/LinkableMigrationTask.php new file mode 100644 index 00000000..2d454cb7 --- /dev/null +++ b/src/Tasks/LinkableMigrationTask.php @@ -0,0 +1,469 @@ + 'LinkField_Link', + self::TABLE_LIVE => 'LinkField_Link_Live', + self::TABLE_VERSIONS => 'LinkField_Link_Versions', + ]; + + protected const TABLE_MAP_EMAIL_LINK = [ + self::TABLE_BASE => 'LinkField_EmailLink', + self::TABLE_LIVE => 'LinkField_EmailLink_Live', + self::TABLE_VERSIONS => 'LinkField_EmailLink_Versions', + ]; + + protected const TABLE_MAP_EXTERNAL_LINK = [ + self::TABLE_BASE => 'LinkField_ExternalLink', + self::TABLE_LIVE => 'LinkField_ExternalLink_Live', + self::TABLE_VERSIONS => 'LinkField_ExternalLink_Versions', + ]; + + protected const TABLE_MAP_FILE_LINK = [ + self::TABLE_BASE => 'LinkField_FileLink', + self::TABLE_LIVE => 'LinkField_FileLink_Live', + self::TABLE_VERSIONS => 'LinkField_FileLink_Versions', + ]; + + protected const TABLE_MAP_PHONE_LINK = [ + self::TABLE_BASE => 'LinkField_PhoneLink', + self::TABLE_LIVE => 'LinkField_PhoneLink_Live', + self::TABLE_VERSIONS => 'LinkField_PhoneLink_Versions', + ]; + + protected const TABLE_MAP_SITE_TREE_LINK = [ + self::TABLE_BASE => 'LinkField_SiteTreeLink', + self::TABLE_LIVE => 'LinkField_SiteTreeLink_Live', + self::TABLE_VERSIONS => 'LinkField_SiteTreeLink_Versions', + ]; + + /** + * @config + * @var string[] + */ + private static $versions_mapping_global = [ + 'RecordID' => 'RecordID', + 'Version' => 'Version', + ]; + + /** + * @config + * @var string[] + */ + private static $versions_mapping_base_only = [ + 'WasPublished' => 'WasPublished', + 'WasDeleted' => 'WasDeleted', + 'WasDraft' => 'WasDraft', + 'AuthorID' => 'AuthorID', + 'PublisherID' => 'PublisherID', + ]; + + /** + * LinkableLink field => LinkField_Link field + * + * @config + * @var string[] + */ + private static $link_mapping = [ + 'ID' => 'ID', + 'LastEdited' => 'LastEdited', + 'Created' => 'Created', + 'Title' => 'Title', + 'OpenInNewWindow' => 'OpenInNew', + ]; + + /** + * LinkableLink field => LinkField_EmailLink field + * + * @config + * @var string[] + */ + private static $email_mapping = [ + 'ID' => 'ID', + 'Email' => 'Email', + ]; + + /** + * LinkableLink field => LinkField_ExternalLink field + * + * @config + * @var string[] + */ + private static $external_mapping = [ + 'ID' => 'ID', + 'URL' => 'ExternalUrl', + ]; + + /** + * LinkableLink field => LinkField_FileLink field + * + * @config + * @var string[] + */ + private static $file_mapping = [ + 'ID' => 'ID', + 'FileID' => 'FileID', + ]; + + /** + * LinkableLink field => LinkField_PhoneLink field + * + * @config + * @var string[] + */ + private static $phone_mapping = [ + 'ID' => 'ID', + 'Phone' => 'Phone', + ]; + + /** + * LinkableLink field => LinkField_SiteTreeLink field + * + * @config + * @var string[] + */ + private static $sitetree_mapping = [ + 'ID' => 'ID', + 'SiteTreeID' => 'PageID', + 'Anchor' => 'Anchor', + ]; + + /** + * @var string + */ + private static $segment = 'linkable-migration-task'; + + /** + * @var string + */ + protected $title = 'Linkable Migration Task'; + + /** + * @var string + */ + protected $description = 'Truncate LinkField records and migrate from Linkable records'; + + /** + * @param HTTPRequest $request + * @return void + * @throws Exception + */ + public function run($request): void + { + // Check that we have matching Versioned states between Linkable and LinkField + if (!$this->versionedStatusMatches()) { + throw new Exception( + 'Linkable and LinkField do not have matching Versioned applications. Make sure that both are' + . ' either un-Versioned or Versioned' + ); + } + + // If we're un-Versioned then it's just going to be the base table + $tables = [ + self::TABLE_BASE, + ]; + + // Since we passed the versionedStatusMatches() step, then we can just check if Link is Versioned, and we can + // safely assume that the Linkable Versioned tables also exist + if (Link::singleton()->hasExtension(Versioned::class)) { + // Add the _Live and _Versions tables to the list of things we need to copy + $tables[] = self::TABLE_LIVE; + $tables[] = self::TABLE_VERSIONS; + } + + // We expect your LinkField tables to be completely clear before migration is kicked off + $this->truncateLinkFieldTables(); + + foreach ($tables as $table) { + // Grab any/all records from the desired table (base, live, versions) + $linkableResults = SQLSelect::create('*', $table)->execute(); + + // Nothing to see here + if ($linkableResults->numRecords() === 0) { + echo sprintf("Nothing to process for `%s`\r\n", $table); + + continue; + } + + echo sprintf("Processing `%s`\r\n", $table); + + // Loop through each DB record + foreach ($linkableResults as $linkableData) { + // We now need to determine what type of Link the original Linkable record was, because we're going to + // have to process each of those slightly differently + switch ($linkableData['Type']) { + case 'Email': + $this->insertEmail($linkableData, $table); + + break; + case 'URL': + $this->insertExternal($linkableData, $table); + + break; + case 'File': + $this->insertFile($linkableData, $table); + + break; + case 'Phone': + $this->insertPhone($linkableData, $table); + + break; + case 'SiteTree': + $this->insertSiteTree($linkableData, $table); + + break; + } + } + + echo sprintf("%d records inserted, finished processing `%s`\r\n", DB::affected_rows(), $table); + } + } + + /** + * Check to see if there is the existence of a _Live table for Linkable (indicating that it was Versioned) + * @return bool + */ + protected function versionedStatusMatches(): bool + { + $wasVersioned = DB::query('SHOW TABLES LIKE \'LinkableLink_Live\';')->numRecords() > 0; + $isVersioned = Link::singleton()->hasExtension(Versioned::class); + + return $wasVersioned === $isVersioned; + } + + /** + * We expect your LinkField tables to be completely clear before migration is kicked off + * This method will delete all data in the new tables providing a clear start and the ability + * to repeat this dev task + * + * @return void + */ + protected function truncateLinkFieldTables(): void + { + $tables = [ + 'LinkField_Link', + 'LinkField_EmailLink', + 'LinkField_ExternalLink', + 'LinkField_FileLink', + 'LinkField_PhoneLink', + 'LinkField_SiteTreeLink', + ]; + $versioned = [ + '_Live', + '_Versions', + ]; + $isVersioned = Link::singleton()->hasExtension(Versioned::class); + + foreach ($tables as $table) { + DB::get_conn()->clearTable($table); + + if (!$isVersioned) { + continue; + } + + foreach ($versioned as $tableSuffix) { + DB::get_conn()->clearTable($table . $tableSuffix); + } + } + } + + /** + * Create assignments from the old field values to the new fields based on provided configuration + * + * @param array $config + * @param array $linkableData + * @param string $originTable + * @return array + */ + protected function getAssignmentsForMapping(array $config, array $linkableData, string $originTable): array + { + // If we're processing the _Versions table, then we need to add all the Version table field assignments + if ($originTable === self::TABLE_VERSIONS) { + $config += $this->config()->get('versions_mapping_global'); + } + + // We're now going to start assigning values to the new fields (as you've specified in your config) + $assignments = []; + + // Loop through each config + foreach ($config as $oldField => $newField) { + // Assign the new field to equal whatever value was in the original record (based on the old field name) + $assignments[$newField] = $linkableData[$oldField]; + } + + return $assignments; + } + + /** + * Create new generic link record based on provided data + * + * @param string $className + * @param array $linkableData + * @param string $originTable + * @return void + */ + protected function insertLink(string $className, array $linkableData, string $originTable): void + { + $config = $this->config()->get('link_mapping'); + + // If we're processing the _Versions table, then we need to add all the Version table field assignments that are + // specifically for the base record (such as all the "WasPublished", "WasDraft", etc fields) + if ($originTable === self::TABLE_VERSIONS) { + $config += $this->config()->get('versions_mapping_global'); + } + + // These assignments are based on our config + $assignments = $this->getAssignmentsForMapping( + $config, + $linkableData, + $originTable + ); + // We also need to add ClassName for the base table, and this is not configurable + $assignments['ClassName'] = $className; + + // Find out what the corresponding table is for the origin table + $newTable = self::TABLE_MAP_LINK[$originTable]; + + // Insert our new record + SQLInsert::create($newTable, $assignments)->execute(); + } + + /** + * Insert new record for email type link + * + * @param array $linkableData + * @param string $originTable + * @return void + */ + protected function insertEmail(array $linkableData, string $originTable): void + { + // Insert the base record for this EmailLink + $this->insertLink(EmailLink::class, $linkableData, $originTable); + + $newTable = self::TABLE_MAP_EMAIL_LINK[$originTable]; + + $assignments = $this->getAssignmentsForMapping( + $this->config()->get('email_mapping'), + $linkableData, + $originTable + ); + + SQLInsert::create($newTable, $assignments)->execute(); + } + + /** + * Insert new record for external type link + * + * @param array $linkableData + * @param string $originTable + * @return void + */ + protected function insertExternal(array $linkableData, string $originTable): void + { + // Insert the base record for this ExternalLink + $this->insertLink(ExternalLink::class, $linkableData, $originTable); + + $newTable = self::TABLE_MAP_EXTERNAL_LINK[$originTable]; + + $assignments = $this->getAssignmentsForMapping( + $this->config()->get('external_mapping'), + $linkableData, + $originTable + ); + + SQLInsert::create($newTable, $assignments)->execute(); + } + + /** + * Insert new record for file type link + * + * @param array $linkableData + * @param string $originTable + * @return void + */ + protected function insertFile(array $linkableData, string $originTable): void + { + // Insert the base record for this FileLink + $this->insertLink(FileLink::class, $linkableData, $originTable); + + $newTable = self::TABLE_MAP_FILE_LINK[$originTable]; + + $assignments = $this->getAssignmentsForMapping( + $this->config()->get('file_mapping'), + $linkableData, + $originTable + ); + + SQLInsert::create($newTable, $assignments)->execute(); + } + + /** + * Insert new record for phone type link + * + * @param array $linkableData + * @param string $originTable + * @return void + */ + protected function insertPhone(array $linkableData, string $originTable): void + { + // Insert the base record for this PhoneLink + $this->insertLink(PhoneLink::class, $linkableData, $originTable); + + $newTable = self::TABLE_MAP_PHONE_LINK[$originTable]; + + $assignments = $this->getAssignmentsForMapping( + $this->config()->get('phone_mapping'), + $linkableData, + $originTable + ); + + SQLInsert::create($newTable, $assignments)->execute(); + } + + /** + * Insert new record for site tree (internal) type link + * + * @param array $linkableData + * @param string $originTable + * @return void + */ + protected function insertSiteTree(array $linkableData, string $originTable): void + { + // Insert the base record for this SiteTreeLink + $this->insertLink(SiteTreeLink::class, $linkableData, $originTable); + + $newTable = self::TABLE_MAP_SITE_TREE_LINK[$originTable]; + + $assignments = $this->getAssignmentsForMapping( + $this->config()->get('sitetree_mapping'), + $linkableData, + $originTable + ); + + SQLInsert::create($newTable, $assignments)->execute(); + } +} diff --git a/tests/php/Models/LinkTest.php b/tests/php/Models/LinkTest.php index e0887792..c5a2d7d0 100644 --- a/tests/php/Models/LinkTest.php +++ b/tests/php/Models/LinkTest.php @@ -9,6 +9,7 @@ use SilverStripe\LinkField\Models\ExternalLink; use SilverStripe\LinkField\Models\FileLink; use SilverStripe\LinkField\Models\Link; +use SilverStripe\LinkField\Models\PhoneLink; use SilverStripe\LinkField\Models\SiteTreeLink; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\ValidationException; @@ -76,6 +77,7 @@ public function linkTypeProvider(): array [EmailLink::class, false], [ExternalLink::class, false], [FileLink::class, false], + [PhoneLink::class, false], [SiteTreeLink::class, false], [Link::class, true], ]; From e054944b4a4d194c06426b03dc0ac85b589ea8a6 Mon Sep 17 00:00:00 2001 From: Mojmir Fendek Date: Thu, 23 Jun 2022 15:44:58 +1200 Subject: [PATCH 09/24] MISC: Github actions added, Travis setup removed. --- .github/workflows/ci.yml | 10 ++++++++++ .travis.yml | 38 -------------------------------------- phpunit.xml.dist | 25 ++++++++++++++++--------- 3 files changed, 26 insertions(+), 47 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..2004851c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,10 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + ci: + uses: silverstripe/gha-ci/.github/workflows/ci.yml@v1 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 683d4b13..00000000 --- a/.travis.yml +++ /dev/null @@ -1,38 +0,0 @@ -version: ~> 1.0 - -import: - - silverstripe/silverstripe-travis-shared:config/provision/standard.yml - -jobs: - include: - - php: 7.4 - env: - - DB=MYSQL - - REQUIRE_INSTALLER="$REQUIRE_RECIPE 4.x-dev" - - PHPUNIT_TEST=1 - - PHPCS_TEST=1 - - PHPSTAN_TEST=1 - - php: 7.4 - env: - - DB=PGSQL - - REQUIRE_INSTALLER="$REQUIRE_RECIPE 4.x-dev" - - PHPUNIT_TEST=1 - - COW_TEST=1 - - php: 7.4 - env: - - DB=MYSQL - - PDO=1 - - REQUIRE_INSTALLER="$REQUIRE_RECIPE 4.x-dev" - - PHPUNIT_COVERAGE_TEST=1 - - php: 8.0 - env: - - DB=MYSQL - - REQUIRE_INSTALLER="$REQUIRE_RECIPE 4.x-dev" - - PHPUNIT_TEST=1 - - REQUIRE_GRAPHQL="^3@dev" - - php: 8.1.0 - env: - - DB=MYSQL - - REQUIRE_INSTALLER="$REQUIRE_RECIPE 4.x-dev" - - PHPUNIT_TEST=1 - - REQUIRE_GRAPHQL="^3@dev" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 1d2d9665..2fdf2b19 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,10 +1,17 @@ - - - - - tests/php - + + + + + src/ + + + tests/ + + + + + tests/ + + From 677ae80e6941e7016b6c3edc1ee1cddef1432efb Mon Sep 17 00:00:00 2001 From: Mojmir Fendek Date: Wed, 15 Feb 2023 15:09:14 +1300 Subject: [PATCH 10/24] BUG: Fix Github actions. --- client/dist/js/bundle.js | 1137 +---------------- client/dist/styles/bundle.css | 3 +- client/src/boot/index.js | 1 - client/src/boot/registerReducers.js | 21 - client/src/components/LinkField/LinkField.js | 48 +- .../src/components/LinkModal/FileLinkModal.js | 25 +- client/src/components/LinkModal/LinkModal.js | 19 +- .../src/components/LinkPicker/LinkPicker.js | 17 +- .../components/LinkPicker/LinkPickerMenu.js | 10 +- .../components/LinkPicker/LinkPickerTitle.js | 9 +- .../LinkPicker/tests/LinkPicker-story.js | 12 +- client/src/entwine/JsonField.js | 8 +- client/src/tests/sample-test.js | 8 + phpcs.xml.dist | 4 +- 14 files changed, 81 insertions(+), 1241 deletions(-) create mode 100644 client/src/tests/sample-test.js diff --git a/client/dist/js/bundle.js b/client/dist/js/bundle.js index 49fabd9f..7c6255c6 100644 --- a/client/dist/js/bundle.js +++ b/client/dist/js/bundle.js @@ -1,1136 +1 @@ -/******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // identity function for calling harmony imports with the correct context -/******/ __webpack_require__.i = function(value) { return value; }; -/******/ -/******/ // define getter function for harmony exports -/******/ __webpack_require__.d = function(exports, name, getter) { -/******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // getDefaultExport function for compatibility with non-harmony modules -/******/ __webpack_require__.n = function(module) { -/******/ var getter = module && module.__esModule ? -/******/ function getDefault() { return module['default']; } : -/******/ function getModuleExports() { return module; }; -/******/ __webpack_require__.d(getter, 'a', getter); -/******/ return getter; -/******/ }; -/******/ -/******/ // Object.prototype.hasOwnProperty.call -/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = "./client/src/bundles/bundle.js"); -/******/ }) -/************************************************************************/ -/******/ ({ - -/***/ "./client/src/boot/index.js": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var _Config = __webpack_require__(6); - -var _Config2 = _interopRequireDefault(_Config); - -var _registerReducers = __webpack_require__("./client/src/boot/registerReducers.js"); - -var _registerReducers2 = _interopRequireDefault(_registerReducers); - -var _registerComponents = __webpack_require__("./client/src/boot/registerComponents.js"); - -var _registerComponents2 = _interopRequireDefault(_registerComponents); - -var _registerQueries = __webpack_require__("./client/src/boot/registerQueries.js"); - -var _registerQueries2 = _interopRequireDefault(_registerQueries); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -document.addEventListener('DOMContentLoaded', function () { - (0, _registerComponents2.default)(); - - (0, _registerQueries2.default)(); - - (0, _registerReducers2.default)(); -}); - -/***/ }), - -/***/ "./client/src/boot/registerComponents.js": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _Injector = __webpack_require__(0); - -var _Injector2 = _interopRequireDefault(_Injector); - -var _LinkPicker = __webpack_require__("./client/src/components/LinkPicker/LinkPicker.js"); - -var _LinkPicker2 = _interopRequireDefault(_LinkPicker); - -var _LinkField = __webpack_require__("./client/src/components/LinkField/LinkField.js"); - -var _LinkField2 = _interopRequireDefault(_LinkField); - -var _LinkModal = __webpack_require__("./client/src/components/LinkModal/LinkModal.js"); - -var _LinkModal2 = _interopRequireDefault(_LinkModal); - -var _FileLinkModal = __webpack_require__("./client/src/components/LinkModal/FileLinkModal.js"); - -var _FileLinkModal2 = _interopRequireDefault(_FileLinkModal); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var registerComponents = function registerComponents() { - _Injector2.default.component.registerMany({ - LinkPicker: _LinkPicker2.default, - LinkField: _LinkField2.default, - 'LinkModal.FormBuilderModal': _LinkModal2.default, - 'LinkModal.InsertMediaModal': _FileLinkModal2.default - }); -}; - -exports.default = registerComponents; - -/***/ }), - -/***/ "./client/src/boot/registerQueries.js": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _Injector = __webpack_require__(0); - -var _Injector2 = _interopRequireDefault(_Injector); - -var _readLinkTypes = __webpack_require__("./client/src/state/linkTypes/readLinkTypes.js"); - -var _readLinkTypes2 = _interopRequireDefault(_readLinkTypes); - -var _readLinkDescription = __webpack_require__("./client/src/state/linkDescription/readLinkDescription.js"); - -var _readLinkDescription2 = _interopRequireDefault(_readLinkDescription); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var registerQueries = function registerQueries() { - _Injector2.default.query.register('readLinkTypes', _readLinkTypes2.default); - _Injector2.default.query.register('readLinkDescription', _readLinkDescription2.default); -}; -exports.default = registerQueries; - -/***/ }), - -/***/ "./client/src/boot/registerReducers.js": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _Injector = __webpack_require__(0); - -var _Injector2 = _interopRequireDefault(_Injector); - -var _redux = __webpack_require__(8); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var registerReducers = function registerReducers() {}; - -exports.default = registerReducers; - -/***/ }), - -/***/ "./client/src/bundles/bundle.js": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -__webpack_require__("./client/src/boot/index.js"); -__webpack_require__("./client/src/entwine/JsonField.js"); - -/***/ }), - -/***/ "./client/src/components/LinkField/LinkField.js": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); - -var _i18n = __webpack_require__(3); - -var _i18n2 = _interopRequireDefault(_i18n); - -var _react = __webpack_require__(1); - -var _react2 = _interopRequireDefault(_react); - -var _reactRedux = __webpack_require__(7); - -var _redux = __webpack_require__(8); - -var _reactApollo = __webpack_require__(13); - -var _Injector = __webpack_require__(0); - -var _FieldHolder = __webpack_require__(9); - -var _FieldHolder2 = _interopRequireDefault(_FieldHolder); - -var _propTypes = __webpack_require__(2); - -var _propTypes2 = _interopRequireDefault(_propTypes); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } - -var LinkField = function LinkField(_ref) { - var id = _ref.id, - loading = _ref.loading, - Loading = _ref.Loading, - data = _ref.data, - LinkPicker = _ref.LinkPicker, - onChange = _ref.onChange, - types = _ref.types, - linkDescription = _ref.linkDescription, - props = _objectWithoutProperties(_ref, ['id', 'loading', 'Loading', 'data', 'LinkPicker', 'onChange', 'types', 'linkDescription']); - - if (loading) { - return _react2.default.createElement(Loading, null); - } - - var _useState = (0, _react.useState)(false), - _useState2 = _slicedToArray(_useState, 2), - editing = _useState2[0], - setEditing = _useState2[1]; - - var _useState3 = (0, _react.useState)(''), - _useState4 = _slicedToArray(_useState3, 2), - newTypeKey = _useState4[0], - setNewTypeKey = _useState4[1]; - - var onClear = function onClear(event) { - typeof onChange === 'function' && onChange(event, { id: id, value: {} }); - }; - - var typeKey = data.typeKey; - - var type = types[typeKey]; - var modalType = newTypeKey ? types[newTypeKey] : type; - - var linkProps = { - title: data ? data.Title : '', - link: type ? { type: type, title: data.Title, description: linkDescription } : undefined, - onEdit: function onEdit() { - setEditing(true); - }, - onClear: onClear, - onSelect: function onSelect(key) { - setNewTypeKey(key); - setEditing(true); - }, - types: Object.values(types) - }; - - var onModalSubmit = function onModalSubmit(data, action, submitFn) { - console.dir({ data: data, action: action, submitFn: submitFn, onChange: onChange }); - - var SecurityID = data.SecurityID, - action_insert = data.action_insert, - value = _objectWithoutProperties(data, ['SecurityID', 'action_insert']); - - typeof onChange === 'function' && onChange(event, { id: id, value: value }); - setEditing(false); - setNewTypeKey(''); - return Promise.resolve(); - }; - - var modalProps = { - type: modalType, - editing: editing, - onSubmit: onModalSubmit, - onClosed: function onClosed() { - setEditing(false); - }, - data: data - }; - - var handlerName = modalType ? modalType.handlerName : 'FormBuilderModal'; - var LinkModal = (0, _Injector.loadComponent)('LinkModal.' + handlerName); - - return _react2.default.createElement( - _react.Fragment, - null, - _react2.default.createElement(LinkPicker, linkProps), - _react2.default.createElement(LinkModal, modalProps) - ); -}; - -var stringifyData = function stringifyData(Component) { - return function (_ref2) { - var data = _ref2.data, - value = _ref2.value, - props = _objectWithoutProperties(_ref2, ['data', 'value']); - - var dataValue = value || data; - if (typeof dataValue === 'string') { - dataValue = JSON.parse(dataValue); - } - return _react2.default.createElement(Component, _extends({ dataStr: JSON.stringify(dataValue) }, props, { data: dataValue })); - }; -}; - -exports.default = (0, _redux.compose)((0, _Injector.inject)(['LinkPicker', 'Loading']), (0, _Injector.injectGraphql)('readLinkTypes'), stringifyData, (0, _Injector.injectGraphql)('readLinkDescription'), _reactApollo.withApollo, _FieldHolder2.default)(LinkField); - -/***/ }), - -/***/ "./client/src/components/LinkModal/FileLinkModal.js": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _i18n = __webpack_require__(3); - -var _i18n2 = _interopRequireDefault(_i18n); - -var _react = __webpack_require__(1); - -var _react2 = _interopRequireDefault(_react); - -var _InsertMediaModal = __webpack_require__(11); - -var _InsertMediaModal2 = _interopRequireDefault(_InsertMediaModal); - -var _reactRedux = __webpack_require__(7); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } - -var FileLinkModal = function FileLinkModal(_ref) { - var type = _ref.type, - editing = _ref.editing, - data = _ref.data, - actions = _ref.actions, - onSubmit = _ref.onSubmit, - props = _objectWithoutProperties(_ref, ['type', 'editing', 'data', 'actions', 'onSubmit']); - - if (!type) { - return false; - } - - (0, _react.useEffect)(function () { - if (editing) { - actions.initModal(); - } else { - actions.reset(); - } - }, [editing]); - - var attrs = data ? { - ID: data.FileID, - Description: data.Title, - TargetBlank: data.OpenInNew ? true : false - } : {}; - - var onInsert = function onInsert(_ref2) { - var ID = _ref2.ID, - Description = _ref2.Description, - TargetBlank = _ref2.TargetBlank; - - return onSubmit({ - FileID: ID, - Title: Description, - OpenInNew: TargetBlank, - typeKey: type.key - }, '', function () {}); - }; - - return _react2.default.createElement(_InsertMediaModal2.default, _extends({ - isOpen: editing, - type: 'insert-link', - title: false, - bodyClassName: 'modal__dialog', - className: 'insert-link__dialog-wrapper--internal', - fileAttributes: attrs, - onInsert: onInsert - }, props)); -}; - -function mapStateToProps() { - return {}; -} - -function mapDispatchToProps(dispatch) { - return { - actions: { - initModal: function initModal() { - return dispatch({ - type: 'INIT_FORM_SCHEMA_STACK', - payload: { formSchema: { type: 'insert-link', nextType: 'admin' } } - }); - }, - reset: function reset() { - return dispatch({ type: 'RESET' }); - } - } - }; -} - -exports.default = (0, _reactRedux.connect)(mapStateToProps, mapDispatchToProps)(FileLinkModal); - -/***/ }), - -/***/ "./client/src/components/LinkModal/LinkModal.js": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _i18n = __webpack_require__(3); - -var _i18n2 = _interopRequireDefault(_i18n); - -var _react = __webpack_require__(1); - -var _react2 = _interopRequireDefault(_react); - -var _propTypes = __webpack_require__(2); - -var _propTypes2 = _interopRequireDefault(_propTypes); - -var _FormBuilderModal = __webpack_require__(10); - -var _FormBuilderModal2 = _interopRequireDefault(_FormBuilderModal); - -var _url = __webpack_require__(12); - -var _url2 = _interopRequireDefault(_url); - -var _qs = __webpack_require__(16); - -var _qs2 = _interopRequireDefault(_qs); - -var _Config = __webpack_require__(6); - -var _Config2 = _interopRequireDefault(_Config); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } - -var leftAndMain = 'SilverStripe\\Admin\\LeftAndMain'; - -var buildSchemaUrl = function buildSchemaUrl(key, data) { - var schemaUrl = _Config2.default.getSection(leftAndMain).form.DynamicLink.schemaUrl; - - var parsedURL = _url2.default.parse(schemaUrl); - var parsedQs = _qs2.default.parse(parsedURL.query); - parsedQs.key = key; - if (data) { - parsedQs.data = JSON.stringify(data); - } - return _url2.default.format(_extends({}, parsedURL, { search: _qs2.default.stringify(parsedQs) })); -}; - -var LinkModal = function LinkModal(_ref) { - var type = _ref.type, - editing = _ref.editing, - data = _ref.data, - props = _objectWithoutProperties(_ref, ['type', 'editing', 'data']); - - if (!type) { - return false; - } - - return _react2.default.createElement(_FormBuilderModal2.default, _extends({ - title: type.title, - isOpen: editing, - schemaUrl: buildSchemaUrl(type.key, data), - identifier: 'Link.EditingLinkInfo' - }, props)); -}; - -exports.default = LinkModal; - -/***/ }), - -/***/ "./client/src/components/LinkPicker/LinkPicker.js": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.Component = undefined; - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _i18n = __webpack_require__(3); - -var _i18n2 = _interopRequireDefault(_i18n); - -var _react = __webpack_require__(1); - -var _react2 = _interopRequireDefault(_react); - -var _Injector = __webpack_require__(0); - -var _propTypes = __webpack_require__(2); - -var _propTypes2 = _interopRequireDefault(_propTypes); - -var _reactstrap = __webpack_require__(4); - -var _classnames = __webpack_require__(5); - -var _classnames2 = _interopRequireDefault(_classnames); - -var _LinkPickerMenu = __webpack_require__("./client/src/components/LinkPicker/LinkPickerMenu.js"); - -var _LinkPickerMenu2 = _interopRequireDefault(_LinkPickerMenu); - -var _LinkPickerTitle = __webpack_require__("./client/src/components/LinkPicker/LinkPickerTitle.js"); - -var _LinkPickerTitle2 = _interopRequireDefault(_LinkPickerTitle); - -var _LinkType = __webpack_require__("./client/src/types/LinkType.js"); - -var _LinkType2 = _interopRequireDefault(_LinkType); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var LinkPicker = function LinkPicker(_ref) { - var types = _ref.types, - onSelect = _ref.onSelect, - link = _ref.link, - onEdit = _ref.onEdit, - onClear = _ref.onClear; - return _react2.default.createElement( - 'div', - { - className: (0, _classnames2.default)('link-picker', 'form-control', { 'link-picker--selected': link }) }, - link === undefined && _react2.default.createElement(_LinkPickerMenu2.default, { types: types, onSelect: onSelect }), - link && _react2.default.createElement(_LinkPickerTitle2.default, _extends({}, link, { onClear: onClear, onClick: function onClick() { - return link && onEdit && onEdit(link); - } })) - ); -}; - -LinkPicker.propTypes = _extends({}, _LinkPickerMenu2.default.propTypes, { - link: _propTypes2.default.shape(_LinkPickerTitle2.default.propTypes), - onEdit: _propTypes2.default.func, - onClear: _propTypes2.default.func -}); - -exports.Component = LinkPicker; -exports.default = LinkPicker; - -/***/ }), - -/***/ "./client/src/components/LinkPicker/LinkPickerMenu.js": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); - -var _i18n = __webpack_require__(3); - -var _i18n2 = _interopRequireDefault(_i18n); - -var _react = __webpack_require__(1); - -var _react2 = _interopRequireDefault(_react); - -var _Injector = __webpack_require__(0); - -var _propTypes = __webpack_require__(2); - -var _propTypes2 = _interopRequireDefault(_propTypes); - -var _reactstrap = __webpack_require__(4); - -var _classnames = __webpack_require__(5); - -var _classnames2 = _interopRequireDefault(_classnames); - -var _LinkType = __webpack_require__("./client/src/types/LinkType.js"); - -var _LinkType2 = _interopRequireDefault(_LinkType); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var LinkPickerMenu = function LinkPickerMenu(_ref) { - var types = _ref.types, - onSelect = _ref.onSelect; - - var _useState = (0, _react.useState)(false), - _useState2 = _slicedToArray(_useState, 2), - isOpen = _useState2[0], - setIsOpen = _useState2[1]; - - var toggle = function toggle() { - return setIsOpen(function (prevState) { - return !prevState; - }); - }; - - return _react2.default.createElement( - _reactstrap.Dropdown, - { - isOpen: isOpen, - toggle: toggle, - className: 'link-picker__menu' - }, - _react2.default.createElement( - _reactstrap.DropdownToggle, - { className: 'link-picker__menu-toggle font-icon-link', caret: true }, - _i18n2.default._t('Link.ADD_LINK', 'Add Link') - ), - _react2.default.createElement( - _reactstrap.DropdownMenu, - null, - types.map(function (_ref2) { - var key = _ref2.key, - title = _ref2.title; - return _react2.default.createElement( - _reactstrap.DropdownItem, - { key: key, onClick: function onClick() { - return onSelect(key); - } }, - title - ); - }) - ) - ); -}; - -LinkPickerMenu.propTypes = { - types: _propTypes2.default.arrayOf(_LinkType2.default).isRequired, - onSelect: _propTypes2.default.func.isRequired -}; - -exports.default = LinkPickerMenu; - -/***/ }), - -/***/ "./client/src/components/LinkPicker/LinkPickerTitle.js": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _i18n = __webpack_require__(3); - -var _i18n2 = _interopRequireDefault(_i18n); - -var _react = __webpack_require__(1); - -var _react2 = _interopRequireDefault(_react); - -var _Injector = __webpack_require__(0); - -var _propTypes = __webpack_require__(2); - -var _propTypes2 = _interopRequireDefault(_propTypes); - -var _classnames = __webpack_require__(5); - -var _classnames2 = _interopRequireDefault(_classnames); - -var _LinkType = __webpack_require__("./client/src/types/LinkType.js"); - -var _LinkType2 = _interopRequireDefault(_LinkType); - -var _reactstrap = __webpack_require__(4); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var stopPropagation = function stopPropagation(fn) { - return function (e) { - console.log('trying to stop propagation'); - e.nativeEvent.stopImmediatePropagation(); - e.preventDefault(); - e.nativeEvent.preventDefault(); - e.stopPropagation(); - fn && fn(); - }; -}; - -var LinkPickerTitle = function LinkPickerTitle(_ref) { - var title = _ref.title, - type = _ref.type, - description = _ref.description, - onClear = _ref.onClear, - onClick = _ref.onClick; - return _react2.default.createElement( - _reactstrap.Button, - { className: 'link-picker__link font-icon-link', color: 'secondary', onClick: stopPropagation(onClick) }, - _react2.default.createElement( - 'div', - { className: 'link-picker__link-detail' }, - _react2.default.createElement( - 'div', - { className: 'link-picker__title' }, - title - ), - _react2.default.createElement( - 'small', - { className: 'link-picker__type' }, - type.title, - ':\xA0', - _react2.default.createElement( - 'span', - { className: 'link-picker__url' }, - description - ) - ) - ), - _react2.default.createElement( - _reactstrap.Button, - { className: 'link-picker__clear', color: 'link', onClick: stopPropagation(onClear) }, - _i18n2.default._t('Link.CLEAR', 'Clear') - ) - ); -}; - -LinkPickerTitle.propTypes = { - title: _propTypes2.default.string.isRequired, - type: _LinkType2.default, - description: _propTypes2.default.string, - onClear: _propTypes2.default.func, - onClick: _propTypes2.default.func -}; - -exports.default = LinkPickerTitle; - -/***/ }), - -/***/ "./client/src/entwine/JsonField.js": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _jquery = __webpack_require__(15); - -var _jquery2 = _interopRequireDefault(_jquery); - -var _react = __webpack_require__(1); - -var _react2 = _interopRequireDefault(_react); - -var _reactDom = __webpack_require__(14); - -var _reactDom2 = _interopRequireDefault(_reactDom); - -var _Injector = __webpack_require__(0); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -_jquery2.default.entwine('ss', function ($) { - $('.js-injector-boot .entwine-jsonfield').entwine({ - - Component: null, - - onmatch: function onmatch() { - var cmsContent = this.closest('.cms-content').attr('id'); - var context = cmsContent ? { context: cmsContent } : {}; - - var schemaComponent = this.data('schema-component'); - var ReactField = (0, _Injector.loadComponent)(schemaComponent, context); - - this.setComponent(ReactField); - this._super(); - this.refresh(); - }, - refresh: function refresh() { - var props = this.getProps(); - var ReactField = this.getComponent(); - _reactDom2.default.render(_react2.default.createElement(ReactField, _extends({}, props, { noHolder: true })), this[0]); - }, - handleChange: function handleChange(event, _ref) { - var id = _ref.id, - value = _ref.value; - - var fieldID = $(this).data('field-id'); - $('#' + fieldID).val(JSON.stringify(value)).trigger('change'); - this.refresh(); - }, - getProps: function getProps() { - var fieldID = $(this).data('field-id'); - var dataStr = $('#' + fieldID).val(); - var value = dataStr ? JSON.parse(dataStr) : undefined; - - return { - id: fieldID, - value: value, - onChange: this.handleChange.bind(this) - }; - }, - onunmatch: function onunmatch() { - _reactDom2.default.unmountComponentAtNode(this[0]); - } - }); -}); - -/***/ }), - -/***/ "./client/src/state/linkDescription/readLinkDescription.js": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _Injector = __webpack_require__(0); - -var apolloConfig = { - props: function props(_props) { - var _props$data = _props.data, - error = _props$data.error, - readLinkDescription = _props$data.readLinkDescription, - networkLoading = _props$data.loading; - - var errors = error && error.graphQLErrors && error.graphQLErrors.map(function (graphQLError) { - return graphQLError.message; - }); - var linkDescription = readLinkDescription ? readLinkDescription.description : ''; - - return { - loading: networkLoading, - linkDescription: linkDescription, - graphQLErrors: errors - }; - } -}; - -var READ = _Injector.graphqlTemplates.READ; - -var query = { - apolloConfig: apolloConfig, - templateName: READ, - pluralName: 'LinkDescription', - pagination: false, - params: { - dataStr: 'String!' - }, - args: { - root: { - dataStr: 'dataStr' - } - }, - fields: ['description'] -}; -exports.default = query; - -/***/ }), - -/***/ "./client/src/state/linkTypes/readLinkTypes.js": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _Injector = __webpack_require__(0); - -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - -var apolloConfig = { - props: function props(_props) { - var _props$data = _props.data, - error = _props$data.error, - readLinkTypes = _props$data.readLinkTypes, - networkLoading = _props$data.loading; - - var errors = error && error.graphQLErrors && error.graphQLErrors.map(function (graphQLError) { - return graphQLError.message; - }); - - var types = readLinkTypes ? readLinkTypes.reduce(function (accumulator, type) { - return _extends({}, accumulator, _defineProperty({}, type.key, type)); - }, {}) : {}; - - return { - loading: networkLoading, - types: types, - graphQLErrors: errors - }; - } -}; - -var READ = _Injector.graphqlTemplates.READ; - -var query = { - apolloConfig: apolloConfig, - templateName: READ, - pluralName: 'LinkTypes', - pagination: false, - params: { - keys: '[ID]' - }, - args: { - root: { - keys: 'keys' - } - }, - fields: ['key', 'title', 'handlerName'] -}; -exports.default = query; - -/***/ }), - -/***/ "./client/src/types/LinkType.js": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _propTypes = __webpack_require__(2); - -var _propTypes2 = _interopRequireDefault(_propTypes); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var LinkType = _propTypes2.default.shape({ - key: _propTypes2.default.string.isRequired, - title: _propTypes2.default.string.isRequired -}); - -exports.default = LinkType; - -/***/ }), - -/***/ 0: -/***/ (function(module, exports) { - -module.exports = Injector; - -/***/ }), - -/***/ 1: -/***/ (function(module, exports) { - -module.exports = React; - -/***/ }), - -/***/ 10: -/***/ (function(module, exports) { - -module.exports = FormBuilderModal; - -/***/ }), - -/***/ 11: -/***/ (function(module, exports) { - -module.exports = InsertMediaModal; - -/***/ }), - -/***/ 12: -/***/ (function(module, exports) { - -module.exports = NodeUrl; - -/***/ }), - -/***/ 13: -/***/ (function(module, exports) { - -module.exports = ReactApollo; - -/***/ }), - -/***/ 14: -/***/ (function(module, exports) { - -module.exports = ReactDom; - -/***/ }), - -/***/ 15: -/***/ (function(module, exports) { - -module.exports = jQuery; - -/***/ }), - -/***/ 16: -/***/ (function(module, exports) { - -module.exports = qs; - -/***/ }), - -/***/ 2: -/***/ (function(module, exports) { - -module.exports = PropTypes; - -/***/ }), - -/***/ 3: -/***/ (function(module, exports) { - -module.exports = i18n; - -/***/ }), - -/***/ 4: -/***/ (function(module, exports) { - -module.exports = Reactstrap; - -/***/ }), - -/***/ 5: -/***/ (function(module, exports) { - -module.exports = classnames; - -/***/ }), - -/***/ 6: -/***/ (function(module, exports) { - -module.exports = Config; - -/***/ }), - -/***/ 7: -/***/ (function(module, exports) { - -module.exports = ReactRedux; - -/***/ }), - -/***/ 8: -/***/ (function(module, exports) { - -module.exports = Redux; - -/***/ }), - -/***/ 9: -/***/ (function(module, exports) { - -module.exports = FieldHolder; - -/***/ }) - -/******/ }); -//# sourceMappingURL=bundle.js.map \ No newline at end of file +!function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var n={};t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s="./client/src/bundles/bundle.js")}({"./client/src/boot/index.js":function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var i=n("./client/src/boot/registerReducers.js"),o=r(i),a=n("./client/src/boot/registerComponents.js"),l=r(a),u=n("./client/src/boot/registerQueries.js"),c=r(u);document.addEventListener("DOMContentLoaded",function(){(0,l.default)(),(0,c.default)(),(0,o.default)()})},"./client/src/boot/registerComponents.js":function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(1),o=r(i),a=n("./client/src/components/LinkPicker/LinkPicker.js"),l=r(a),u=n("./client/src/components/LinkField/LinkField.js"),c=r(u),s=n("./client/src/components/LinkModal/LinkModal.js"),d=r(s),f=n("./client/src/components/LinkModal/FileLinkModal.js"),p=r(f),y=function(){o.default.component.registerMany({LinkPicker:l.default,LinkField:c.default,"LinkModal.FormBuilderModal":d.default,"LinkModal.InsertMediaModal":p.default})};t.default=y},"./client/src/boot/registerQueries.js":function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(1),o=r(i),a=n("./client/src/state/linkTypes/readLinkTypes.js"),l=r(a),u=n("./client/src/state/linkDescription/readLinkDescription.js"),c=r(u),s=function(){o.default.query.register("readLinkTypes",l.default),o.default.query.register("readLinkDescription",c.default)};t.default=s},"./client/src/boot/registerReducers.js":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){};t.default=r},"./client/src/bundles/bundle.js":function(e,t,n){"use strict";n("./client/src/boot/index.js"),n("./client/src/entwine/JsonField.js")},"./client/src/components/LinkField/LinkField.js":function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n={};for(var r in e)t.indexOf(r)>=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}Object.defineProperty(t,"__esModule",{value:!0});var o=Object.assign||function(e){for(var t=1;t=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}function o(){return{}}function a(e){return{actions:{initModal:function(){return e({type:"INIT_FORM_SCHEMA_STACK",payload:{formSchema:{type:"insert-link",nextType:"admin"}}})},reset:function(){return e({type:"RESET"})}}}}Object.defineProperty(t,"__esModule",{value:!0});var l=Object.assign||function(e){for(var t=1;t=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}Object.defineProperty(t,"__esModule",{value:!0});var o=Object.assign||function(e){for(var t=1;t { - // Injector.reducer.register('assetAdmin', combineReducers({ - // gallery, - // queuedFiles, - // uploadField, - // previewField, - // imageLoad, - // displaySearch, - // confirmDeletion, - // modal - // })); }; export default registerReducers; diff --git a/client/src/components/LinkField/LinkField.js b/client/src/components/LinkField/LinkField.js index a5173eaa..56ad3e08 100644 --- a/client/src/components/LinkField/LinkField.js +++ b/client/src/components/LinkField/LinkField.js @@ -1,23 +1,22 @@ -import i18n from 'i18n'; -import React, { Component, Fragment, useState } from 'react'; -import { connect } from 'react-redux'; -import { bindActionCreators, compose } from 'redux'; +import React, { Component, Fragment, useState } from 'react'; // eslint-disable-line no-unused-vars +import { compose } from 'redux'; import { withApollo } from 'react-apollo'; import { inject, injectGraphql, loadComponent } from 'lib/Injector'; import fieldHolder from 'components/FieldHolder/FieldHolder'; -import PropTypes from 'prop-types'; -const LinkField = ({id, loading, Loading, data, LinkPicker, onChange, types, linkDescription, ...props}) => { +const LinkField = ( + { id, loading, Loading, data, LinkPicker, onChange, types, linkDescription } +) => { if (loading) { - return + return ; } const [editing, setEditing] = useState(false); const [newTypeKey, setNewTypeKey] = useState(''); const onClear = (event) => { - typeof onChange === 'function' && onChange(event, { id, value: {}}) - } + typeof onChange === 'function' && onChange(event, { id, value: {} }); // eslint-disable-line no-unused-expressions + }; const { typeKey } = data; const type = types[typeKey]; @@ -26,24 +25,23 @@ const LinkField = ({id, loading, Loading, data, LinkPicker, onChange, types, lin const linkProps = { title: data ? data.Title : '', - link: type ? {type, title: data.Title, description: linkDescription} : undefined, - onEdit: () => {setEditing(true)}, + link: type ? { type, title: data.Title, description: linkDescription } : undefined, + onEdit: () => { setEditing(true); }, onClear, onSelect: (key) => { setNewTypeKey(key); setEditing(true); }, types: Object.values(types) - } + }; - const onModalSubmit = (data, action, submitFn) => { - console.dir({data, action, submitFn, onChange}); - const {SecurityID, action_insert, ...value} = data; - typeof onChange === 'function' && onChange(event, { id, value}) + const onModalSubmit = (submitData, action, submitFn) => { // eslint-disable-line no-unused-vars + const { SecurityID, action_insert, ...value } = submitData; // eslint-disable-line camelcase + typeof onChange === 'function' && onChange(event, { id, value }); // eslint-disable-line no-unused-expressions setEditing(false); setNewTypeKey(''); return Promise.resolve(); - } + }; const modalProps = { type: modalType, @@ -56,15 +54,17 @@ const LinkField = ({id, loading, Loading, data, LinkPicker, onChange, types, lin }; const handlerName = modalType ? modalType.handlerName : 'FormBuilderModal'; - const LinkModal = loadComponent(`LinkModal.${handlerName}`) + const LinkModal = loadComponent(`LinkModal.${handlerName}`); - return - - - ; -} + return ( + + + ); +}; -const stringifyData = (Component) => ( ({data, value, ...props}) => { +const stringifyData = (Component) => (( // eslint-disable-line no-shadow + { data, value, ...props } +) => { let dataValue = value || data; if (typeof dataValue === 'string') { dataValue = JSON.parse(dataValue); diff --git a/client/src/components/LinkModal/FileLinkModal.js b/client/src/components/LinkModal/FileLinkModal.js index 5313e6f5..9536988a 100644 --- a/client/src/components/LinkModal/FileLinkModal.js +++ b/client/src/components/LinkModal/FileLinkModal.js @@ -1,11 +1,9 @@ /* global tinymce, editorIdentifier, ss */ -import i18n from 'i18n'; -import React, {useEffect} from 'react'; -import InsertMediaModal from 'containers/InsertMediaModal/InsertMediaModal'; -import {connect} from "react-redux"; - -const FileLinkModal = ({type, editing, data, actions, onSubmit, ...props}) => { +import React, { useEffect } from 'react'; +import InsertMediaModal from 'containers/InsertMediaModal/InsertMediaModal'; // eslint-disable-line import/no-unresolved, import/extensions +import { connect } from 'react-redux'; +const FileLinkModal = ({ type, editing, data, actions, onSubmit, ...props }) => { if (!type) { return false; } @@ -16,24 +14,23 @@ const FileLinkModal = ({type, editing, data, actions, onSubmit, ...props}) => { } else { actions.reset(); } - }, [editing]) + }, [editing]); const attrs = data ? { ID: data.FileID, Description: data.Title, - TargetBlank: data.OpenInNew ? true : false, + TargetBlank: !!data.OpenInNew, } : {}; - const onInsert = ({ID, Description, TargetBlank}) => { - return onSubmit({ + const onInsert = (ID, Description, TargetBlank) => + onSubmit({ FileID: ID, Title: Description, OpenInNew: TargetBlank, typeKey: type.key }, '', () => {}); - }; - return { fileAttributes={attrs} onInsert={onInsert} {...props} - />; -} + />); +}; function mapStateToProps() { return {}; diff --git a/client/src/components/LinkModal/LinkModal.js b/client/src/components/LinkModal/LinkModal.js index 84948346..1cf9f48f 100644 --- a/client/src/components/LinkModal/LinkModal.js +++ b/client/src/components/LinkModal/LinkModal.js @@ -1,6 +1,4 @@ -import i18n from 'i18n'; import React from 'react'; -import PropTypes from 'prop-types'; import FormBuilderModal from 'components/FormBuilderModal/FormBuilderModal'; import url from 'url'; import qs from 'qs'; @@ -9,8 +7,7 @@ import Config from 'lib/Config'; const leftAndMain = 'SilverStripe\\Admin\\LeftAndMain'; const buildSchemaUrl = (key, data) => { - - const {schemaUrl} = Config.getSection(leftAndMain).form.DynamicLink; + const { schemaUrl } = Config.getSection(leftAndMain).form.DynamicLink; const parsedURL = url.parse(schemaUrl); const parsedQs = qs.parse(parsedURL.query); @@ -18,21 +15,21 @@ const buildSchemaUrl = (key, data) => { if (data) { parsedQs.data = JSON.stringify(data); } - return url.format({ ...parsedURL, search: qs.stringify(parsedQs)}); -} + return url.format({ ...parsedURL, search: qs.stringify(parsedQs) }); +}; -const LinkModal = ({type, editing, data, ...props}) => { +const LinkModal = ({ type, editing, data, ...props }) => { if (!type) { return false; } - return ; -} + />); +}; export default LinkModal; diff --git a/client/src/components/LinkPicker/LinkPicker.js b/client/src/components/LinkPicker/LinkPicker.js index 3ec8eab0..d4c05fcf 100644 --- a/client/src/components/LinkPicker/LinkPicker.js +++ b/client/src/components/LinkPicker/LinkPicker.js @@ -1,18 +1,19 @@ -import i18n from 'i18n'; import React from 'react'; -import { inject } from 'lib/Injector'; import PropTypes from 'prop-types'; -import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem, Button } from 'reactstrap'; import classnames from 'classnames'; import LinkPickerMenu from './LinkPickerMenu'; import LinkPickerTitle from './LinkPickerTitle'; -import LinkType from 'types/LinkType'; const LinkPicker = ({ types, onSelect, link, onEdit, onClear }) => ( -
+
{link === undefined && } - {link && link && onEdit && onEdit(link)}/>} + { + link && link && onEdit && onEdit(link)} + /> + }
); @@ -24,6 +25,6 @@ LinkPicker.propTypes = { }; -export {LinkPicker as Component}; +export { LinkPicker as Component }; export default LinkPicker; diff --git a/client/src/components/LinkPicker/LinkPickerMenu.js b/client/src/components/LinkPicker/LinkPickerMenu.js index 3a1ac555..42f21f7d 100644 --- a/client/src/components/LinkPicker/LinkPickerMenu.js +++ b/client/src/components/LinkPicker/LinkPickerMenu.js @@ -1,9 +1,7 @@ import i18n from 'i18n'; -import React, {useState, setState} from 'react'; -import { inject } from 'lib/Injector'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'; -import classnames from 'classnames'; import LinkType from 'types/LinkType'; const LinkPickerMenu = ({ types, onSelect }) => { @@ -19,11 +17,11 @@ const LinkPickerMenu = ({ types, onSelect }) => { > {i18n._t('Link.ADD_LINK', 'Add Link')} - {types.map(({key, title}) => - onSelect(key)}>{title} + {types.map(({ key, title }) => + onSelect(key)}>{title} )} - + ); }; diff --git a/client/src/components/LinkPicker/LinkPickerTitle.js b/client/src/components/LinkPicker/LinkPickerTitle.js index 38229d0f..64d41f2e 100644 --- a/client/src/components/LinkPicker/LinkPickerTitle.js +++ b/client/src/components/LinkPicker/LinkPickerTitle.js @@ -1,19 +1,16 @@ import i18n from 'i18n'; import React from 'react'; -import { inject } from 'lib/Injector'; import PropTypes from 'prop-types'; -import classnames from 'classnames'; import LinkType from 'types/LinkType'; -import {Button} from 'reactstrap'; +import { Button } from 'reactstrap'; const stopPropagation = (fn) => (e) => { - console.log('trying to stop propagation'); e.nativeEvent.stopImmediatePropagation(); e.preventDefault(); e.nativeEvent.preventDefault(); e.stopPropagation(); - fn && fn(); -} + fn && fn(); // eslint-disable-line no-unused-expressions +}; const LinkPickerTitle = ({ title, type, description, onClear, onClick }) => (