Skip to content

Commit

Permalink
NEW Link type icon
Browse files Browse the repository at this point in the history
  • Loading branch information
Sabina Talipova committed Jan 14, 2024
1 parent fe72946 commit 9dce205
Show file tree
Hide file tree
Showing 18 changed files with 164 additions and 7 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,30 @@ use SilverStripe\Versioned\Versioned;
Link::remove_extension(Versioned::class);
```

## Additional features

The developer can customize the position of the link type in the menu by setting the `$menu_priority` value. The priority is set ascending, i.e. the link will be displayed in the menu above if the priority value is less. By default, the highest priority has SiteTreeLink equal to zero.
The developer can also set an icon that will correspond to a specific type of link by setting the value of the `$icons` property. This name must correspond to the name of the class of the icon that needs to be used.

```yml
SilverStripe\LinkField\Models\PhoneLink:
icon: 'font-icon-menu-help'
menu_priority: 1
```
The developer can also define these values for a new link type.
```php
<?php

use SilverStripe\LinkField\Models\Link;

class MyCustomLink extends Link
{
private static int $menu_priority = 1;
private static $icon = 'font-icon-custom';
}
```

## Migrating from Shae Dawson's Linkable module

https://github.com/sheadawson/silverstripe-linkable
Expand Down
2 changes: 1 addition & 1 deletion client/dist/js/bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion client/dist/styles/bundle.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/src/components/LinkField/LinkField.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ const LinkField = ({
description={data[linkID]?.description}
versionState={data[linkID]?.versionState}
typeTitle={type.title || ''}
typeIcon={type.icon || 'font-icon-link'}
onDelete={onDelete}
onClick={() => { setEditingID(linkID); }}
canDelete={data[linkID]?.canDelete ? true : false}
Expand Down
5 changes: 5 additions & 0 deletions client/src/components/LinkPicker/LinkPicker.scss
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
}
}

.link-picker__menu-icon {
vertical-align: middle;
padding-right: 0.7rem;
}

.link-picker__link {
@extend %link-row;

Expand Down
7 changes: 5 additions & 2 deletions client/src/components/LinkPicker/LinkPickerMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ const LinkPickerMenu = ({ types, onSelect }) => {
{i18n._t('LinkField.ADD_LINK', 'Add Link')}
</DropdownToggle>
<DropdownMenu>
{types.map(({key, title}) =>
<DropdownItem key={key} onClick={() => onSelect(key)}>{title}</DropdownItem>
{types.map(({key, title, icon}) =>
<DropdownItem key={key} onClick={() => onSelect(key)}>
<span className={`link-picker__menu-icon ${icon}`}></span>
{title}
</DropdownItem>
)}
</DropdownMenu>
</Dropdown>
Expand Down
4 changes: 3 additions & 1 deletion client/src/components/LinkPicker/LinkPickerTitle.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const LinkPickerTitle = ({
description,
versionState,
typeTitle,
typeIcon,
onDelete,
onClick,
canDelete
Expand All @@ -51,7 +52,7 @@ const LinkPickerTitle = ({
? i18n._t('LinkField.DELETE', 'Delete')
: i18n._t('LinkField.ARCHIVE', 'Archive');
return <div className={className}>
<Button className="link-picker__button font-icon-link" color="secondary" onClick={stopPropagation(onClick)}>
<Button className={`link-picker__button ${typeIcon}`} color="secondary" onClick={stopPropagation(onClick)}>
<div className="link-picker__link-detail">
<div className="link-picker__title">
<span className="link-picker__title-text">{title}</span>
Expand All @@ -75,6 +76,7 @@ LinkPickerTitle.propTypes = {
description: PropTypes.string,
versionState: PropTypes.string,
typeTitle: PropTypes.string.isRequired,
typeIcon: PropTypes.string.isRequired,
onDelete: PropTypes.func.isRequired,
onClick: PropTypes.func.isRequired,
canDelete: PropTypes.bool.isRequired,
Expand Down
10 changes: 9 additions & 1 deletion client/src/components/LinkPicker/tests/LinkPicker-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import LinkPicker from '../LinkPicker';

function makeProps(obj = {}) {
return {
types: { phone: { key: 'phone', title: 'Phone' } },
types: { phone: { key: 'phone', title: 'Phone', icon: 'font-icon-phone' } },
onModalSuccess: () => {},
onModalClosed: () => {},
...obj
Expand All @@ -29,3 +29,11 @@ test('LinkPickerMenu render() should display cannot create message if cannot cre
expect(container.querySelectorAll('.link-picker__menu-toggle')).toHaveLength(0);
expect(container.querySelectorAll('.link-picker__cannot-create')).toHaveLength(1);
});

test('LinkPickerMenu render() should display link type icon if can create', () => {
const { container } = render(<LinkPicker {...makeProps({
canCreate: true
})}
/>);
expect(container.querySelectorAll('.link-picker__menu-icon.font-icon-phone')).toHaveLength(1);
});
10 changes: 10 additions & 0 deletions client/src/components/LinkPicker/tests/LinkPickerTitle-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ function makeProps(obj = {}) {
description: 'My description',
versionState: 'draft',
typeTitle: 'Phone',
typeIcon: 'font-icon-phone',
onDelete: () => {},
onClick: () => {},
...obj
Expand All @@ -23,6 +24,7 @@ test('LinkPickerTitle render() should display clear button if can delete', () =>
})}
/>);
expect(container.querySelectorAll('.link-picker__delete')).toHaveLength(1);
expect(container.querySelectorAll('.font-icon-phone')).toHaveLength(1);
});

test('LinkPickerTitle render() should not display clear button if cannot delete', () => {
Expand All @@ -32,3 +34,11 @@ test('LinkPickerTitle render() should not display clear button if cannot delete'
/>);
expect(container.querySelectorAll('.link-picker__delete')).toHaveLength(0);
});

test('LinkPickerTitle render() should display link type icon', () => {
const { container } = render(<LinkPickerTitle {...makeProps({
canDelete: false
})}
/>);
expect(container.querySelectorAll('.font-icon-phone')).toHaveLength(1);
});
1 change: 1 addition & 0 deletions client/src/types/LinkType.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
const LinkType = PropTypes.shape({
key: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
icon: PropTypes.string.isRequired,
});

export default LinkType;
1 change: 1 addition & 0 deletions src/Form/Traits/AllowedLinkClassesTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public function getTypesProps(): string
'title' => $type->getMenuTitle(),
'handlerName' => $type->LinkTypeHandlerName(),
'priority' => $class::config()->get('menu_priority'),
'icon' => $type->getLinkIcon(),
];
}
uasort($typesList, function ($a, $b) {
Expand Down
5 changes: 5 additions & 0 deletions src/Models/EmailLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ class EmailLink extends Link
*/
private static int $menu_priority = 30;

/**
* Set the default icon for the Email link type
*/
private static $icon = 'font-icon-p-mail';

public function getCMSFields(): FieldList
{
$this->beforeUpdateCMSFields(function (FieldList $fields) {
Expand Down
5 changes: 5 additions & 0 deletions src/Models/ExternalLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ class ExternalLink extends Link
*/
private static int $menu_priority = 20;

/**
* Set the default icon for the External link type
*/
private static $icon = 'font-icon-external-link';

public function getCMSFields(): FieldList
{
$this->beforeUpdateCMSFields(function (FieldList $fields) {
Expand Down
5 changes: 5 additions & 0 deletions src/Models/FileLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ class FileLink extends Link
*/
private static int $menu_priority = 10;

/**
* Set the default icon for the File link type
*/
private static $icon = 'font-icon-image';

public function getCMSFields(): FieldList
{
$this->beforeUpdateCMSFields(function (FieldList $fields) {
Expand Down
10 changes: 10 additions & 0 deletions src/Models/Link.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ class Link extends DataObject
*/
private static int $menu_priority = 100;

/**
* Set the default icon for the link type
*/
private static $icon = 'font-icon-link';

public function getDescription(): string
{
return '';
Expand Down Expand Up @@ -470,4 +475,9 @@ public function getMenuTitle(): string
{
return $this->i18n_singular_name();
}

public function getLinkIcon(): string
{
return static::config()->get('icon');
}
}
5 changes: 5 additions & 0 deletions src/Models/PhoneLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ class PhoneLink extends Link
* Set the priority of this link type in the CMS menu
*/
private static int $menu_priority = 40;

/**
* Set the default icon for the Phone link type
*/
private static $icon = 'font-icon-block-phone';

public function getCMSFields(): FieldList
{
Expand Down
5 changes: 5 additions & 0 deletions src/Models/SiteTreeLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ class SiteTreeLink extends Link
*/
private static int $menu_priority = 0;

/**
* Set the default icon for the SiteTree link type
*/
private static $icon = 'font-icon-page';

public function getDescription(): string
{
$page = $this->Page();
Expand Down
69 changes: 68 additions & 1 deletion tests/php/Traits/AllowedLinkClassesTraitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ public function sortedTypesDataProvider() : array
/**
* @dataProvider sortedTypesDataProvider
*/
public function testGetTypesProps(array $enabled, array $expected, bool $reorder): void
public function testGetSortedTypeProps(array $enabled, array $expected, bool $reorder): void
{
if ($reorder) {
Injector::inst()->get(TestPhoneLink::class)->config()->set('menu_priority', 5);
Expand All @@ -190,4 +190,71 @@ public function testGetTypesPropsCanCreate(): void
$this->assertTrue(array_key_exists('sitetree', $json));
$this->assertFalse(array_key_exists('testphone', $json));
}

public function typePropsDataProvider() : array
{
return [
'SiteTreeLink props' => [
'class' => SiteTreeLink::class,
'key' => 'sitetree',
'title' => 'Page on this site',
'priority' => 0,
'icon' => 'font-icon-page',
],
'EmailLink props' => [
'class' => EmailLink::class,
'key' => 'email',
'title' => 'Link to email address',
'priority' => 30,
'icon' => 'font-icon-p-mail',
],
'ExternalLink props' => [
'class' => ExternalLink::class,
'key' => 'external',
'title' => 'Link to external URL',
'priority' => 20,
'icon' => 'font-icon-external-link',
],
'FileLink props' => [
'class' => FileLink::class,
'key' => 'file',
'title' => 'Link to a file',
'priority' => 10,
'icon' => 'font-icon-image',
],
'PhoneLink props' => [
'class' => PhoneLink::class,
'key' => 'phone',
'title' => 'Phone number',
'priority' => 40,
'icon' => 'font-icon-block-phone',
],
'TestPhoneLink props' => [
'class' => TestPhoneLink::class,
'key' => 'testphone',
'title' => 'Test Phone Link',
'priority' => 100,
'icon' => 'font-icon-link',
],
];
}

/**
* @dataProvider typePropsDataProvider
*/
public function testGetTypesProps(
string $class,
string $key,
string $title,
int $priority,
string $icon
): void {
$linkField = LinkField::create('LinkField');
$linkField->setAllowedTypes([$class]);
$json = json_decode($linkField->getTypesProps(), true);
$this->assertEquals($key, $json[$key]['key']);
$this->assertEquals($title, $json[$key]['title']);
$this->assertEquals($priority, $json[$key]['priority']);
$this->assertEquals($icon, $json[$key]['icon']);
}
}

0 comments on commit 9dce205

Please sign in to comment.