Appearance
Module-based extensions
Module-based extensions let you use the full power of JavaScript or TypeScript to define your PopClip extension. This allows you use code to construct properties like options at load time, and to define actions dynamically, for example to generate titles or icons in response to the input text.
When you provide a config file called Config.js or Config.ts, PopClip treats this as a JavaScript or TypeScript module and looks for the extension's properties in the exported object, after first loading static properties from YAML in a comment header.
All properties exported by the module will be merged into the extension's config, overriding any static properties with the same name (except for the static-only properties which cannot be overriden).
The module can also define a population function to dynamically populate the actions.
Snippets as modules
You can also define a module in a snippet by setting module: true.
Example
The following JavaScript snippet defines a complete module-based extension:
javascript
// #popclip
// name: Module Demo
// after: show-result
// language: javascript
// module: true
// this is only run once, at load time
const theNumber = String(Math.floor(Math.random() * 100));
module.exports = {
icon: `square ${theNumber}`,
actions: [
{
title: "The Title",
code: (input) => {
return `The number is ${theNumber}. Your text is: ${input.text}`;
},
},
],
};Observe a few things:
- The extension's
nameand the action'safterstep,show-result, are specified in the static config in the header. - At load time, the module generates a random number and saves it in a variable.
- The module exports an
iconproperty, displaying the random number in a square. - The module defines its actions by exporting an
actionsarray. See Module actions.
More examples
See the following examples from the PopClip Extensions Directory:
File format
Comment header
In Config.js and Config.ts a YAML comment header must be provided defining the extension's name and any other static-only properties. The header is in the same format as for a snippet (see Snippets - Inverted syntax) except that you do not specify language or module in the header. The file is automatically loaded as a module.
Module format
The module file may be written in JavaScript (.js) or TypeScript (.ts).
The module format is CommonJS. You can either export a single object with module.exports = ... or export individual properties like exports.foo = ....
TypeScript files can use ES Modules syntax, which will be transpiled to CommonJS under the hood. JavaScript files may not use ES Modules syntax.
The exported property names and types are the same as defined in Config, with the execption of actions which has special handling - see Module actions.
Specifying the module file
The module does not have to be loaded from Config.js/Config.ts. Alternatively, you can provide static config in another format (e.g. Config.json) and specify a module file name as follows:
| Key | Type | Description |
|---|---|---|
module | String | The path to a JavaScript (.js) or TypeScript (.ts) file to load. |
Static-only properties
Certain properties of the extension can only be defined in the static config, and cannot be overriden by the module. These are identifier, popclipVersion, macosVersion and entitlements.
Module actions
Detailed API reference
A more detailed definition of the action object, action function and population function may be found in the JavaScript API Reference.
A module defines its actions by exporting an actions property, which can be either:
- an array of action objects, or
- a population function returning an array of action objects.
Note that a module always provides all the actions for the extension. You cannot mix regular actions and module actions in the same extension.
Action object
Each action object has the same properties as a regular action, with the addition of the following:
| Key | Type | Description |
|---|---|---|
code | Function | A function to run when the action is invoked. See: Action function. |
regex | RegExp Object | You may export a JavaScript RegExp, and PopClip will use this instead of a string regex. |
Action function
The action function is called with the following arguments:
input: same object aspopclip.inputoptions: same object aspopclip.optionscontext: same object aspopclip.context
javascript
{
code: ((input, options, context) => {
// ... do stuff ...
doSomething();
return someResult;
});
}javascript
{
code: (async (input, options, context) => {
// ... do stuff ...
await doSomethingAsync();
return someResult;
});
}The function may return a string, which will be passed to the after step. Otherwise it should return undefined or null.
The function may optionally be async, and use await.
The function may indicate an error by throwing an exception, as per JavaScript actions.
Population function
Entitlement needed
To use a population function, the dynamic entitlement must be present in the entitlements array in the static config. This cannot be set if the network entitlement is also being used.
The population function is set as the actions property of the module. It dynamically supplies actions every time the PopClip bar appears. The population function is called with the same arguments as the action function, and it returns an array of action objects.
javascript
// #popclip dynamic example
// { name: Dynamic Title, entitlements: [dynamic], lang: js, module: true }
exports.actions = (input, options, context) => {
return [{
title: `<${input.text.slice(0, 10)}>`,
code: (input, options, context) => {
popclip.showText("Hi from Action");
},
}];
};typescript
// #popclip dynamic example
// { name: Dynamic Title, entitlements: [dynamic], lang: ts, module: true }
export const actions: PopulationFunction = (input, options, context) => {
return [{
title: `<${input.text.slice(0, 10)}>`,
code: (input, options, context) => {
popclip.showText("Hi from Action");
},
}];
};The population function has the following limitations:
- Cannot access the network (
XMLHttpRequestis unavailable). - Cannot call methods on the
popclipglobal object. - Cannot access
popclip.context.browserUrlorpopclip.context.browserTitle.
Abbreviated forms
The action property
If the module defines only a single action, it may be exported as the action property instead of in an actions array. For example:
javascript
// #popclip
// { name: Single Action, lang: js, module: true}
exports.action = {
code: () => {
popclip.showText("hi mom!");
},
};typescript
// #popclip
// { name: Single Action, lang: ts, module: true}
export const action: Action = {
code: () => {
popclip.showText("hi mom!");
},
};Action function shorthand
If the action object has only a code property, it may be exported as a function instead of an object. For example:
javascript
// #popclip
// { name: Action Function, lang: js, module: true}
exports.action = () => {
popclip.showText("hi mom!");
};typescript
// #popclip
// { name: Action Function, lang: ts, module: true}
export const action: ActionFunction = () => {
popclip.showText("hi mom!");
};