Paneron extensions are written in TypeScript and use React components.
Point of focus of an extension is repository view. Repository view is essentially a React component that takes no props. That component is rendered by Paneron, and given some primitives for accessing and changing repository data (those primitives can be accessed through extension view context).
Note
|
These guidelines are for Paneron 1.0.0-alpha17. Before that version the process was slightly different. Specifying PANERON_PLUGIN_DIR environment variable is no longer suggested. |
-
Think of an ID for your extension (e.g., my-extension).
-
On GitHub, create a new repository using Paneron extension template repository (https://github.com/paneron/extension-template-basic) or registry extension template repository (https://github.com/paneron/extension-template-registry).
-
Clone the repository you’ve created and edit package.json to fill in your extension name, description, and author (see placeholders).
-
Inside Paneron app data directory, create subdirectory chain 'plugins/@riboseinc/paneron-extension-my-extension'.
That is where Paneron will expect your extension built form to reside.
-
For testing purposes, obtain Paneron source code (clone https://github.com/paneron/paneron) and build the project locally.
After you make changes:
-
Rebuild your extension from its repository root:
$ yarn build
-
Make sure to add the
dist/
directory to local extension list in Paneron settings. -
Launch (relaunch) Paneron.
An extension must be released as a separate NPM package, specifying entry as plugin.js
.
That entry file should call makeExtension()
imported from the extension kit,
passing it extension configuration.
Note
|
See the template extension for example packaging. |
When ready & tested, release your extension on NPM
by running this command from within dist/
subdirectory
in your extension repository:
$ npm publish --access public
Note
|
--access public is only necessary first time you publish the extension. (See NPM CLI docs.)
|
Note
|
Currently, only extensions released under @riboseinc and @paneron NPM scopes are supported.
Plugin name must either match @riboseinc/paneron-extension-foo-registry
or @paneron/extension-foo-registry .
|
There are some extra constraints placed upon extension component code, mostly due to limitations of extension mechanism at this point in time.
-
An extension must not have any dependencies specified in package.json. Anything you want to import should be provided by Paneron (see below what is). (You can use devDependencies, though.)
-
An extension cannot call browser window’s native functions. If you need
setTimeout()
, use Node’ssetTimeout
. UsingBlob
oraddEventListener()
is not possible currently.If this rule is not observed, you may end up getting a confusing blank screen after opening a repository using your extension and “DevTools was disconnected from the page” in console.
These are available for import in extensions.
Add them (and, where applicable, the corresponding "@types/*" counterparts)
as devDependencies
in your extension’s package.json for development.
(Do not specify any dependencies
.)
Note
|
Versions matter, for specific versions that will be provided at runtime by Paneron
see Paneron’s package.json .
|
Paneron extension building kits:
-
"@riboseinc/paneron-extension-kit" (major and minor version match Paneron’s)
-
"@riboseinc/paneron-registry-kit" (major and minor version match Paneron’s)
Basic React and related:
-
"react"
-
"@emotion/react" (don’t forget to add JSX pragma to your files)
-
"@emotion/styled"
GUI components:
-
"react-flow-renderer"
-
"react-window"
-
"react-mathjax2" (Paneron already provides MathJax context for you; just use the components where needed)
-
"react-visual-diff"
-
"react-helmet" (you can use it for setting window title)
Blueprint 4:
-
"@blueprintjs/core"
-
"@blueprintjs/datetime"
-
"@blueprintjs/icons"
-
"@blueprintjs/select"
-
"@blueprintjs/popover2"
General purpose libs:
-
"liquidjs"
-
"date-fns"
-
"js-yaml"
-
"jsondiffpatch"
-
"throttle-debounce"
-
"async-mutex"
-
"immutability-helper"
-
"immer"
-
"ramda"
3D output libs:
-
"three"
-
"three-stdlib"
-
"@react-three/fiber"
-
"@react-three/drei"
Here’s how a simple component can be written using Emotion:
/** @jsx jsx */
import React, { useContext, useState } from 'react';
import { css, jsx } from '@emotion/react';
import { ExtensionViewContext } from '@riboseinc/paneron-extension-kit/context';
const MyRepositoryView: React.FC<Record<never, never>> = function () {
const { title } = useContext(ExtensionViewContext);
const [value, setValue] = useState(null);
return <div css={css`padding: 1rem;`}>Welcome to repository {title}!</div>;
};
Note
|
If using React fragment shorthand syntax ( /** @jsx jsx */
/** @jsxFrag React.Fragment */
import React, { useContext, useState } from 'react';
import { css, jsx } from '@emotion/react';
import { ExtensionViewContext } from '@riboseinc/paneron-extension-kit/context';
const MyRepositoryView: React.FC<Record<never, never>> = function () {
const { title } = useContext(ExtensionViewContext);
const [value, setValue] = useState(null);
return <>
<div css={css`padding: 1rem;`}>Welcome to repository {title}!</div>
</>;
}; |