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 6644853
Show file tree
Hide file tree
Showing 18 changed files with 144 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 customise the position of the link type in the menu by setting the `$menu_priority` value. The priority is in ascending order (i.e. a link with a higher priority value will be displayed lower in the list).
The developer can also set an icon that will correspond to a specific type of link by setting the value of the `$icon` configuration property. The value of this configuration corresponds to the css class of the icon 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}
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' => $class::config()->get('icon'),
];
}
uasort($typesList, function ($a, $b) {
Expand Down
2 changes: 2 additions & 0 deletions src/Models/EmailLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class EmailLink extends Link
*/
private static int $menu_priority = 30;

private static $icon = 'font-icon-p-mail';

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

private static $icon = 'font-icon-external-link';

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

private static $icon = 'font-icon-image';

public function getCMSFields(): FieldList
{
$this->beforeUpdateCMSFields(function (FieldList $fields) {
Expand Down
5 changes: 5 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;

/**
* The css class for the icon to display for this link type
*/
private static $icon = 'font-icon-link';

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

private static $icon = 'font-icon-mobile';

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

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-mobile',
],
'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 6644853

Please sign in to comment.