Icons panelwindow.EasySvgPanel.registerCollections

window.EasySvgPanel.registerCollections()

Lets an add-on list its own icon collections inside the Icon library tab, next to the site's icons, and optionally move, rename, add or export them.

JavaScript · Easy SVG · Status: Stable

registerCollections() lets your add-on show its own icons in Easy SVG's Icon library tab. You hand over a provider: an object with an id and a load function that returns your collections. The library lists them in its sidebar, puts their icons into the same grid and search as the site's own icons, and offers editing actions only for what your provider implements.

Usage

window.EasySvgPanel.registerCollections( {
	id: 'myaddon', // required
	load: ( { signal } ) => Promise.resolve( collections ), // required

	// All optional:
	move, create, rename, trash,
	relabel, remove, add, recolour, replaceDrawing,
	importFile, addMedia,
	history, restoreVersion,
	exportIcons, exportMax,
	hint,
} );

When to use it

  • Your add-on has icons of its own (a theme folder, a bundled icon set, sets stored in a custom post type) and people should find them where they already look for icons.
  • You want the library's search, grid and copy buttons for those icons without building a tab.
  • Your add-on works on sites with the free plugin alone. Unlike registerTab(), collections are not tied to an Easy SVG Pro licence.

When not to use it:

  • To render icons on the front end. That is easy_svg_icon() and the easy_svg_icon_markup filter in PHP. The panel only displays what you hand it.
  • To change the site's own library icons. Those belong to Easy SVG's REST routes.
  • When the panel is older than contract level 3: the method does not exist there.

Parameters

body
provider.idstring
Required

A unique id for your provider. A second call with the same id replaces the provider. Prefix it with your add-on's slug.

body
provider.loadfunction
Required

Called as load( { signal } ) and must return an array of collections, or a Promise of one. signal is an AbortSignal; pass it to fetch(), because the library aborts a running load when it starts a new one or when the tab closes. Each collection is an object with:

  • id (string, required): unique across all providers. Do not use all or library, which are the library's own views.
  • label (string): the name in the sidebar. Falls back to the id.
  • readonly (boolean): when true, no action is offered on this collection or its icons.
  • icons (array, required): the icons, each with key (unique tile key, falls back to ref), slug, ref (the address a template uses, in the form collection/slug), label and content (the SVG markup, already sanitized and hardened by you).

Any other fields you add to a collection, such as a post id, come back to you unchanged in the collection argument of the optional methods.

body
provider.movefunction

move( { refs, to } ), called when tiles are dropped on one of your non read-only collections or sent there through the tile menu. refs is an array of up to 25 refs (larger selections arrive in batches), to the target collection id. Resolve to { message, undo }; undo is optional and returns a Promise of { message }.

body
provider.create, rename, trashfunction

Managing collections from the sidebar. create( { label } ) resolves to { id, message } and the library switches to the new id. rename( { id, label, collection } ) resolves to { message }. trash( { id, collection } ) resolves to { message, undo }. Only the first provider that has create gets the "new collection" control.

body
provider.relabel, remove, add, recolour, replaceDrawingfunction

Working on the icons inside one collection. relabel( { ref, label, collection } ), remove( { refs, collection } ) (said with Undo, not asked twice), add( { icons, to, collection } ) where icons is a list of name plus markup (files dropped, picked or pasted while your collection is open go here), recolour( { refs, collection } ) and replaceDrawing( { ref, markup, collection } ). Each resolves to { message }, optionally with undo; add may also return rows as a per-file list of file, ok and message.

body
provider.importFile, addMediafunction

importFile( { file, collection } ) receives a ZIP or an SVG font (a file with glyph elements) dropped on an open collection. addMedia( { ids, collection } ) receives media library attachment ids. Both resolve like add.

body
provider.history, restoreVersionfunction

Earlier drawings of an icon. history( { ref, collection } ) resolves to { versions, note }, each version with id, date, since, markup and current. restoreVersion( { ref, version, collection } ) resolves to { message }. Both must exist for the action to appear. For icons of the site's own library collection is null, and the first provider with both methods is asked.

body
provider.exportIcons, exportMaxfunction, number

exportIcons( { refs } ) exports the picked tiles from any collections and delivers the file itself; resolve to { message }. With a finite exportMax, Export is not offered when more tiles are picked. Only the first provider with exportIcons is used.

body
provider.hintobject

A note in the sidebar, for example when a feature your collections need is switched off: { text, action: { label, run } }, where run returns a Promise. The action is optional.

Return value

returnundefined

Nothing. A provider without id or without a load function is ignored silently.

Default

No providers are registered by the free plugin. Without readonly, a collection counts as writable, but the library still offers only the actions whose methods exist.

Examples

A read-only collection from inline data

The minimum: one collection, two icons, nothing editable. The markup here is static and already clean.

( function () {
	const panel = window.EasySvgPanel;
	if ( ! panel || 'function' !== typeof panel.registerCollections ) {
		return;
	}

	const icons = [
		{
			key: 'myaddon-brand/logo',
			slug: 'logo',
			ref: 'myaddon-brand/logo',
			label: 'Logo',
			content: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/></svg>',
		},
		{
			key: 'myaddon-brand/mark',
			slug: 'mark',
			ref: 'myaddon-brand/mark',
			label: 'Mark',
			content: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 20 12 4l8 16z"/></svg>',
		},
	];

	panel.registerCollections( {
		id: 'myaddon',
		load: () => [ { id: 'myaddon-brand', label: 'Brand', readonly: true, icons } ],
	} );
} )();

Load collections from your REST route

A real provider asks the server, passes the abort signal on and caches the answer, because load() runs every time the library opens and every time anything is registered.

( function () {
	const panel = window.EasySvgPanel;
	if ( ! panel || 'function' !== typeof panel.registerCollections ) {
		return;
	}

	const config = window.EasySvgPanelData || {};
	let cache = null;

	function request( path, options = {} ) {
		return fetch( config.restRoot + 'myaddon/v1' + path, {
			credentials: 'same-origin',
			...options,
			headers: {
				'Content-Type': 'application/json',
				'X-WP-Nonce': config.nonce,
			},
		} ).then( ( response ) =>
			response.json().then( ( data ) =>
				response.ok ? data : Promise.reject( new Error( data.message || response.statusText ) )
			)
		);
	}

	panel.registerCollections( {
		id: 'myaddon',
		load: ( { signal } ) => {
			if ( cache ) {
				return Promise.resolve( cache );
			}
			return request( '/collections', { signal } ).then( ( list ) => {
				cache = list;
				return list;
			} );
		},
		relabel: ( { ref, label } ) => {
			cache = null;
			return request( '/relabel', {
				method: 'POST',
				body: JSON.stringify( { ref, label } ),
			} );
		},
	} );
} )();

window.EasySvgPanelData is the panel's own localized data; reading restRoot and nonce from it saves you localizing them again. Your routes must answer { message } for actions and must check capabilities themselves.

Accept moves, with undo

move() receives refs of tiles from anywhere in the library. Return an undo when you can reverse the move.

window.EasySvgPanel.registerCollections( {
	id: 'myaddon',
	load: myaddonLoad,
	move: async ( { refs, to } ) => {
		const done = await myaddonPost( '/move', { refs, to } );
		return {
			message: done.message,
			undo: () => myaddonPost( '/move-back', { token: done.token } ),
		};
	},
} );

Defensive: only on contract level 3

( function () {
	const panel = window.EasySvgPanel;
	const level = panel ? Number( panel.api || 1 ) : 0;

	if ( 3 > level || 'function' !== typeof panel.registerCollections ) {
		return; // Older panel: no collections in the library.
	}

	panel.registerCollections( { id: 'myaddon', load: () => [] } );
} )();

How it works

The Icon library tab (src/panel/tabs/Library.jsx) does all the work:

  1. When it loads. On mount, and again on every registry notification (any registerTab() or registerCollections() call), the tab aborts the previous load and calls load( { signal } ) on every provider, wrapped in Promise.resolve().
  2. What it keeps. A result that is not an array counts as no collections, and a rejected Promise is swallowed the same way. Collections without an id or without an icons array are dropped. Each kept collection remembers its provider.
  3. The sidebar. It appears once there is a collection, a provider with create or a hint. It lists All icons, Icon library, then every collection with its icon count. A hint from any provider is shown there.
  4. The grid and the search. In All icons the library's icons and every collection's icons share one grid; selecting a collection shows only its icons. The search filters your icons in the browser by label, slug and ref. Tiles are keyed by key, else ref.
  5. Drawing. Your content is drawn as markup after a second pass through DOMPurify with its SVG profile, as defence in depth.
  6. Actions. An action appears only when your provider has the method and the collection is not readonly. The library waits for your Promise, shows its message (or "Done."), offers Undo when you returned undo, shows error.message when you rejected, and reloads all collections afterwards.

The library tab is only drawn for users who hold its capability (manage_options by default), and its content appears only while the icon library feature is switched on. When it is off, the tab shows a notice instead. Your load() is still called when the tab opens, but nothing of its answer is shown.

Pitfalls

  • load() runs often. Every time the library opens and every time any add-on registers anything. Cache, and pass signal to your requests.
  • Errors in load() vanish. A rejection simply shows no collections. Log the error yourself while developing.
  • Collection ids must be unique across all providers and must not be all or library.
  • Keep refs in the form collection/slug. When tiles are moved, refs that already start with the target id and a slash are skipped as "already there".
  • Sanitize on the server. The panel's DOMPurify pass is a safety net, not your sanitizer. Clean markup with easy_svg_sanitizer() before you send it.
  • readonly turns off everything for that collection, including moves into it.
  • One exporter, one creator. If several providers implement exportIcons or create, only the first one registered is used.
  • Off means off. With the icon library switched off in Settings, your collections are not shown at all, and your hint is not shown either, although load() still runs.

Markup you return as content, markup or in history versions is inserted into an admin page. Hand over only SVG that went through the site's sanitizer, and let every endpoint behind move, add, remove and the other methods check current_user_can() and the wp_rest nonce. The library offers an action based on your JavaScript; it does not check permissions for you.