Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NEW Link type icon #170

Merged
merged 1 commit into from
Jan 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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']);
}
}