Quantcast
Channel: Artur Piszek
Viewing all articles
Browse latest Browse all 24

Augmenting WordPress autocompleter quick links with your own post type

$
0
0

WordPress has a quick way to link to your other notes without copy-pasting. If you type [[ (just as in in Roam Research), you will invoke a dropdown to search your posts and pages. Like so:

This is called an “autocompleter”. It’s the same dropdown that powers block inserter (invoked by /) and the people-tagger (invoked by @).

You can see the source of the links completer here.

How to add your post type to autocompleter

I am currently writing a WordPress-based notetaking system that aims to simplify your entire knowledge management stack, from reading to publishing, all inside WordPress.

As part of this endeavour, I am introducing a Note post type that will never get published, but can be embedded and linked from everywhere and to everywhere.

And I want this “Note” post type to show in the completer alongsited Posts and Pages whenever you type [[. There are 2 problems with this, however:

  1. As you can see in the above source, the completer uses the `/wp/v2/search’` endpoint, which only searches public-facing content of the site, so it will not work for my Notes post type.
  2. The completer for the [[ command already exists and there is no way to augment that specific one. So what should we do?

Merging completers

What if we had a third completer that would merge the result of 2 others and serve as a “unified” completer?

Let’s do that! First, we need a completer for our Note post type. This completer will insert a Note block instead of linking to the note, but it could link by returning <a> in getOptionCompletion.

const NoteCompleter = {
	name: 'links',
	className: 'block-editor-autocompleters__link',
	triggerPrefix: '[[',
	options: async ( letters ) => {
		let options = await apiFetch( {
			path: addQueryArgs( '/pos/v1/notes', {
				per_page: 10,
				search: letters,
			} ),
		} );
	
		options = options.map( ( { id, title, type, excerpt } ) => ( {
			id,
			title: title.rendered,
			type,
			excerpt: excerpt.rendered.replace( /(<([^>]+)>)/gi, '' ).substring( 0, 100 ),
		} ) );

		return options;
	},
	getOptionKeywords( item ) {
		const expansionWords = item.title.split( /\s+/ );
		const experptWords = item.excerpt.split( /\s+/ );
		return [ ...expansionWords, ...experptWords ];
	},
	getOptionLabel( item ) {
		return (
			<>
				<Icon
					key="icon"
					icon={ overlayText }
				/>
				{ item.title || item.excerpt }
			</>
		);
	},
	getOptionCompletion( item ) {
		return {
			action: 'replace',
			value: createBlock( 'pos/note', {
				note_id: item.id,
			} ),
		}
	},
}

In order to merge this completer with the links from core, we will need this mergeCompleters function:

  • It assumes name, classname and triggerPrefix are similar for all these completers
  • It will trigger `options` methods in parallel and merge their results
  • Each result will have a completer index attached, so that the expansion gets handled by appropriate getOptionKeywords, getOptionLabel and getOptionCompletion from the original completers
function mergeCompleters( completers ) {
	return {
		name: completers[0].name,
		className: completers[0].className,
		triggerPrefix: completers[0].triggerPrefix,
		options: async ( letters ) => {
			const completerResults = await Promise.all(
				completers.map( completer => completer.options( letters ) )
			);
			const opt = completerResults.map( ( completer, completerId ) => completer.map( option => ( { ...option, completer: completerId } ) ) ).flat();
			return opt;
		},
		getOptionKeywords: ( item ) => completers[item.completer].getOptionKeywords( item ),
		getOptionLabel: ( item ) => completers[item.completer].getOptionLabel( item ),
		getOptionCompletion: ( item ) => completers[item.completer].getOptionCompletion( item ),
	}
}

And now we hook into editor.Autocomplete.completers

  • We filter out original links completer
  • Merge it with our NotesCompleter
  • And feed back to to the completer list

function appendMergedCompleter( completers, blockName ) {
	const linksCompleter = completers.find( ( { name } ) => name === 'links' );
	const allCompleters = completers.filter( ( { name } ) => name !== 'links' );
    return [ mergeCompleters( [ linksCompleter, NoteCompleter ] ), ...allCompleters ]
}


// Adding the filter
wp.hooks.addFilter(
    'editor.Autocomplete.completers',
    'pos/autocompleters/links-and-notes',
    appendMergedCompleter
);

And now we have the Notes post type alongside regular results:

The post Augmenting WordPress autocompleter quick links with your own post type appeared first on Artur Piszek.


Viewing all articles
Browse latest Browse all 24

Trending Articles