diff --git a/dev-test/config.yml b/dev-test/config.yml index 2ffe5041..adc72647 100644 --- a/dev-test/config.yml +++ b/dev-test/config.yml @@ -62,6 +62,7 @@ collections: # A list of collections the CMS should be able to edit - name: 'settings' label: 'Settings' + icon: 'settings' delete: false # Prevent users from deleting documents in this collection editor: preview: false diff --git a/dev-test/index.html b/dev-test/index.html index cde4d1d4..11d07243 100644 --- a/dev-test/index.html +++ b/dev-test/index.html @@ -237,6 +237,7 @@ ); } }); + CMS.registerAdditionalLink('Example.com', 'https://example.com/', 'new-tab'); diff --git a/packages/netlify-cms-app/src/extensions.js b/packages/netlify-cms-app/src/extensions.js index 9a71b254..c09f1a9b 100644 --- a/packages/netlify-cms-app/src/extensions.js +++ b/packages/netlify-cms-app/src/extensions.js @@ -1,3 +1,4 @@ +import React from 'react'; // Core import { NetlifyCmsCore as CMS } from 'netlify-cms-core'; // Backends @@ -29,6 +30,7 @@ import NetlifyCmsWidgetColorString from 'netlify-cms-widget-colorstring'; import image from 'netlify-cms-editor-component-image'; // Locales import * as locales from 'netlify-cms-locales'; +import { images, Icon } from 'netlify-cms-ui-default'; // Register all the things CMS.registerBackend('git-gateway', GitGatewayBackend); @@ -66,3 +68,7 @@ CMS.registerEditorComponent({ Object.keys(locales).forEach(locale => { CMS.registerLocale(locale, locales[locale]); }); + +Object.keys(images).forEach(iconName => { + CMS.registerIcon(iconName, ); +}); diff --git a/packages/netlify-cms-core/index.d.ts b/packages/netlify-cms-core/index.d.ts index 42c689f2..fdb7b5a0 100644 --- a/packages/netlify-cms-core/index.d.ts +++ b/packages/netlify-cms-core/index.d.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ declare module 'netlify-cms-core' { - import type { ComponentType } from 'react'; + import type { ComponentType, ReactNode } from 'react'; import type { List, Map } from 'immutable'; import type { Pluggable } from 'unified'; @@ -578,7 +578,10 @@ declare module 'netlify-cms-core' { widgetName: string, serializer: CmsWidgetValueSerializer, ) => void; - resolveWidget: (name: string) => CmsWidget | undefined; + registerIcon: (iconName: string, icon: ReactNode) => void; + getIcon: (iconName: string) => ReactNode; + registerAdditionalLink: (title: string, link: string, iconName?: string) => void; + getAdditionalLinks: () => { title: string, link: string, iconName?: string }[]; } export const NetlifyCmsCore: CMS; diff --git a/packages/netlify-cms-core/src/components/Collection/Sidebar.js b/packages/netlify-cms-core/src/components/Collection/Sidebar.js index fdae657f..fa046b0a 100644 --- a/packages/netlify-cms-core/src/components/Collection/Sidebar.js +++ b/packages/netlify-cms-core/src/components/Collection/Sidebar.js @@ -10,6 +10,7 @@ import { Icon, components, colors } from 'netlify-cms-ui-default'; import { searchCollections } from '../../actions/collections'; import CollectionSearch from './CollectionSearch'; import NestedCollection from './NestedCollection'; +import { getAdditionalLinks, getIcon } from '../../lib/registry'; const styles = { sidebarNavLinkActive: css` @@ -67,6 +68,26 @@ const SidebarNavLink = styled(NavLink)` `}; `; +const AdditionalLink = styled.a` + display: flex; + font-size: 14px; + font-weight: 500; + align-items: center; + padding: 8px 12px; + border-left: 2px solid #fff; + z-index: -1; + + ${Icon} { + margin-right: 8px; + flex-shrink: 0; + } + + &:hover, + &:active { + ${styles.sidebarNavLinkActive}; + } +`; + export class Sidebar extends React.Component { static propTypes = { collections: ImmutablePropTypes.map.isRequired, @@ -79,6 +100,14 @@ export class Sidebar extends React.Component { renderLink = (collection, filterTerm) => { const collectionName = collection.get('name'); + const iconName = collection.get('icon'); + let icon = ; + if (iconName) { + const storedIcon = getIcon(iconName); + if (storedIcon) { + icon = storedIcon; + } + } if (collection.has('nested')) { return (
  • @@ -97,7 +126,60 @@ export class Sidebar extends React.Component { activeClassName="sidebar-active" data-testid={collectionName} > - + {icon} + {collection.get('label')} + +
  • + ); + }; + + renderAdditionalLink = ({ title, url, iconName }) => { + let icon = ; + if (iconName) { + const storedIcon = getIcon(iconName); + if (storedIcon) { + icon = storedIcon; + } + } + return ( +
  • + + {icon} + {title} + +
  • + ); + }; + + renderLink = (collection, filterTerm) => { + const collectionName = collection.get('name'); + const iconName = collection.get('icon'); + let icon = ; + if (iconName) { + const storedIcon = getIcon(iconName); + if (storedIcon) { + icon = storedIcon; + } + } + if (collection.has('nested')) { + return ( +
  • + +
  • + ); + } + return ( +
  • + + {icon} {collection.get('label')}
  • @@ -106,6 +188,7 @@ export class Sidebar extends React.Component { render() { const { collections, collection, isSearchEnabled, searchTerm, t, filterTerm } = this.props; + const additionalLinks = getAdditionalLinks(); return ( {t('collection.sidebar.collections')} @@ -122,6 +205,7 @@ export class Sidebar extends React.Component { .toList() .filter(collection => collection.get('hide') !== true) .map(collection => this.renderLink(collection, filterTerm))} + {additionalLinks.map(this.renderAdditionalLink)} ); diff --git a/packages/netlify-cms-core/src/lib/registry.js b/packages/netlify-cms-core/src/lib/registry.js index 3f27ddf1..114b0e96 100644 --- a/packages/netlify-cms-core/src/lib/registry.js +++ b/packages/netlify-cms-core/src/lib/registry.js @@ -25,6 +25,8 @@ const registry = { templates: {}, previewStyles: [], widgets: {}, + icons: {}, + additionalLinks: [], editorComponents: Map(), remarkPlugins: [], widgetValueSerializers: {}, @@ -58,6 +60,10 @@ export default { removeEventListener, getEventListeners, invokeEvent, + registerIcon, + getIcon, + registerAdditionalLink, + getAdditionalLinks }; /** @@ -284,3 +290,23 @@ export function registerLocale(locale, phrases) { export function getLocale(locale) { return registry.locales[locale]; } + +/** + * Icons + */ +export function registerIcon(name, icon) { + registry.icons[name] = icon; +} +export function getIcon(name) { + return registry.icons[name]; +} + +/** + * Icons + */ +export function registerAdditionalLink(title, url, iconName) { + registry.additionalLinks.push({ title, url, iconName }); +} +export function getAdditionalLinks() { + return registry.additionalLinks; +} diff --git a/packages/netlify-cms-ui-default/src/index.js b/packages/netlify-cms-ui-default/src/index.js index b9bb4339..20d8900c 100644 --- a/packages/netlify-cms-ui-default/src/index.js +++ b/packages/netlify-cms-ui-default/src/index.js @@ -14,6 +14,7 @@ import AuthenticationPage from './AuthenticationPage'; import WidgetPreviewContainer from './WidgetPreviewContainer'; import ObjectWidgetTopBar from './ObjectWidgetTopBar'; import GoBackButton from './GoBackButton'; +import images from './Icon/images/_index'; import { fonts, colorsRaw, @@ -63,6 +64,7 @@ export const NetlifyCmsUiDefault = { zIndex, reactSelectStyles, GlobalStyles, + images, }; export { Dropdown, @@ -97,4 +99,5 @@ export { reactSelectStyles, GlobalStyles, GoBackButton, + images, };