Skip to content

Latest commit

 

History

History
181 lines (132 loc) · 5.64 KB

EXTENSION_DEVELOPMENT.adoc

File metadata and controls

181 lines (132 loc) · 5.64 KB

Extension development

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.

Setup using GitHub as repository host

  1. Think of an ID for your extension (e.g., my-extension).

  2. 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).

  3. Clone the repository you’ve created and edit package.json to fill in your extension name, description, and author (see placeholders).

  4. 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.

  5. For testing purposes, obtain Paneron source code (clone https://github.com/paneron/paneron) and build the project locally.

Iteration

After you make changes:

  1. Rebuild your extension from its repository root:

    $ yarn build
  2. Make sure to add the dist/ directory to local extension list in Paneron settings.

  3. Launch (relaunch) Paneron.

Release

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.

Rules of Paneron extensions

There are some extra constraints placed upon extension component code, mostly due to limitations of extension mechanism at this point in time.

  1. 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.)

  2. An extension cannot call browser window’s native functions. If you need setTimeout(), use Node’s setTimeout. Using Blob or addEventListener() 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.

Modules you can import

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"

Using Emotion for styling components

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 (<>…</>), use @jsxFrag pragma:

/** @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>
  </>;
};