Plugin Configuration

ULLD documentation for developers

ULLD supports several types of plugins. Developers can build plugins that integrate with ULLD seemlessly and provide one more more of the following features:

  • Embeddable Components: Components that can be embedded in the user's mdx content.
  • Slot Components: Components that override specific component slots in the user's compiled application.
  • Parsing Functions: Functions that partially parse mdx content before it is rendered to React, adding the ability to create new syntaxes unique to your specific plugin.

Developer Config Schema

ULLD exports a zod object from @ulld/configschema/developer as a named export, developerConfigSchema as well as a DeveloperConfigInput type. ULLD also exports a simple utility function, writePluginConfig from @ulld/developer/writePluginConfig. This approach was taken to allow you, the developer, to ensure that your configuration can be successfully parsed before publishing your plugin to npm. With these exports, a file can be created at the root of your package (this already exists if you clone the developer template) which can then be executed with ts-node or tsx generate a completely type safe json file named pluginConfig.ulld.json, also at the root of your project.

<root>/createMyConfig.ts
import {
    DeveloperConfigInput,
    developerConfigSchema,
} from "@ulld/configschema/developer";
import { writePluginConfig } from "@ulld/developer/writePluginConfig";
 
const pluginConfig: DeveloperConfigInput = {
    ...
};
 
const parsedPlugin = developerConfigSchema.parse(pluginConfig);
 
 
writePluginConfig(parsedPlugin, __dirname);
PropTypeDefault
pluginName
string
-
label
string
-
slot
string | number | symbol
-
components
ComponentConfig<T>[]
-
parsers
{ mdx?: ParserConfig | undefined; }
-
additionalImports
AdditionalImportsConfig
-
trpc
TrpcConfig
-
settings
PluginSettingsConfig
-
pages
PluginPageConfig<T>[]
-
events
PluginEventsConfig
-
navigationLinks
NavigationLinkType[]
-

Slot

Optional

The slot field in the DeveloperConfigInput type refers to an existing slot in the user's entire application. These slots, mostly, don't affect the user's embedded components but rather their compiled application as a whole. If you do override a slot in the user's compiled application, you do not need to override all subSlots in that slot.

If your pluginConfig.ulld.json, which ideally is defined directly through the developerConfigSchema zod object specifies a slot of type keyof SlotMap, then 1 or more of your components can specify a slot which is a key of the internal object, or a second level key of SlotMap.

For example, if your pluginConfig.ulld.json specifies a slot property of navigation, then your components can optionally override navbar or secondary by setting the developerConfigSchema.components[number].slot path to navbar, secondary, or any key of SlotMap["navigation"]. These components will be passed the same props as the internally built component and be rendered in the same location in the DOM.

Hint:

This SlotMap type is exported from @ulld/configschema/developerTypes, and a matching Typescript object is exported from @ulld/utilities/slotMap.

@ulld/configschema/developerTypes
interface SlotMap {
    "snippets": {
        SnippetListPage: string,
        AddSnippetPage: string,
        SnippetList: string,
        SnippetFilter: string,
    }, 
    "math": {
        EquationDetailsModalPage: string,
        EquationsPage: string,
        EquationDetailsPage: string,
        AddEquationPage: string,
    }, 
    "editor": {
        EditorModalPage: string,
        MdxEditorPage: string,
        LatexEditorPage: string,
        EditorPage: string,
    }, 
    "UI": {
        ComponentDocsListPage: string,
        Pagination: string,
        logo: string,
        loadingIndicator: string,
        confirmationModal: string,
        ComponentDocumentationPageWrapper: string,
        AutoSettingsTable: string,
        AutoSettingFormModal: string,
        SearchResultsPage: string,
        BookmarksPage: string,
        NotesSearchResultsList: string,
        NoteSummarySearchResult: string,
        DictionaryLetterList: string,
        DictionaryPageItem: string,
        NoteTypeSecondaryFilter: string,
        NoteTypeSearchResultList: string,
        MdxNoteContainer: string,
    }, 
    "bibliography": {
        BibliographyPage: string,
        BibEntryDetails: string,
    }, 
    "taskManager": {
        TaskManagerPage: string,
        TaskDetails: string,
        AddTask: string,
        AddTaskList: string,
        TaskListSearchResultList: string,
        TaskListSearchResult: string,
    }, 
    "pdf": {
        FullPdf: string,
    }, 
    "navigation": {
        secondary: string,
        NoteDetailSheet: string,
        navbar: string,
        MultiPageSidebar: string,
        FullScreenNavigationMenu: string,
        Footer: string,
    }, 
    "dashboard": {
        LandingPage: string,
    }, 
    "form": {
        SettingsPageContainer: string,
    }, 
    "commandPalette": {
        commandPalette: string,
    }, 
}

Components

Important

It is important to remember that all components, along with most ULLD plugin exports, must be the default export from the file they are contained in. The only real exception to this rule is the PageProps type discussed here, which is optionally exported as a type alongside a default exported component.

The DeveloperConfigInput["components"] path is an array of objects which maps items from the exports field in your package.json file to locations within the user's compiled application, with some properties that you can set as a developer. These components can take one of two broad types:

  1. An embeddable component, which the user can include directly in their mdx content.
  2. A slot component, which can override slots that are by default occupied by internally developed ULLD components.
PropTypeDefault
componentName
string
-
slot
T extends keyof SlotMap ? keyof SlotMap[T] : string
-
export
string
-
embeddable
EmbeddableConfig | EmbeddableConfig[]
-
docsExport
string
-
fullDocsExport
string
-
exportedPropsName
string
-

Of the above properties, the following are universal, regardless if the component is an embeddable component or a slot component.

componentName

The name of the component. This is used for generating files, and more importantly, for creating default mdx embeds. Like all React components, make sure it starts with a capital letter. This will be handled internally as well, but being too reliant on parsing this after the fact may lead to unforeseen bugs.

export

Note:

Your files will be ran through a bundler as your app is compiled. There's no need to bundle your app as javascript before hand, and in fact, it might actually complicate the build process. Unless you built your components in Javascript originally, export the .tsx file directly.

This must match an export field in your package.json file. For example, take the following truncated package.json file:

{
"name": "myPluginForTheAwesomeULLDFramework",
    "files": [
        "src"
    ],
    "exports": {
        "./myShortenedPath": "./src/path/to/actual/file.tsx"
    }
}

You can now include a component in your developerConfigSchema object as:

import {
    DeveloperConfigInput,
    developerConfigSchema,
} from "@ulld/configschema/developer";
import { writePluginConfig } from "@ulld/developer/writePluginConfig";
 
const parsedPlugin = developerConfigSchema.parse({
    components: [{
        componentName: "MyAwesomeComponent",
        export: "./myShortenedPath",
        // export: "/myShortenedPath" or this
        // export: "myShortenedPath" or this
    }],
    // ...,
});
 
writePluginConfig(parsedPlugin, __dirname);
Hint:

Make sure the component is the default export from the <root>/src/path/to/actual/file.tsx file, or whichever path you add to your package.json file.

Embeddable Components

Embeddable components are the most simple plugin feature to develop for ULLD. Like all components exported by your plugin, they must be a default export and the both DeveloperConfigInput["components"][number]["export"] and DeveloperConfigInput["components"][number]["componentName"] properties are required.

The slot property does not apply to embeddable components, as they will be made available directly to the user and not applied elsewhere. What is required however, is the embeddable field. The embeddable field is either an object, or an array of objects of the following type:

Hint:

An array can be used to create aliases for the same component. This is the case with internal components like the Highlight component, which can also be used as Hl. The component will not be imported twice, even if it is used with multiple aliases.

PropTypeDefault
regexToInclude
string
-
label
string
-

The regexToInclude property will be passed to a RegExp class and used to test a note's raw content for whether or not your component should be imported. 99% of the time, this field should take the form of:

const myComponentConfig: DeveloperConfigInput["components"][number]["embeddable"] = {
    ...,
    regexToInclude: "<MyComponentNameOrAlias" 
}

Where MyComponentNameOrAlias is the name of the component as it will be usable to the user. This does not necessarily have to match the name of the component within your code base, and the leading < makes sure that that value is being used to create a jsx component.

The label field is optional, but if provided should match the regexToInclude property apart from any special characters. If none is provided, this will be generated automatically. The only time this really should be overridden is during debugging, or if the output of the developerConfigSchema produces a value at this field that doesn't seem to make sense.

Documenting your component

The component configuration type also supports two documentation fields, docsExport and fullDocsExport. These should be either markdown or mdx files, but make sure not to include components other than your own individual component and default ULLD internal components. Even other components within your own plugin are not guaranteed to be available for all users, as users can choose to override components that serve a similar purpose.

While neither documentation field is strictly required for your component, it is heavily preferred that you include at least a docsExport field to summarize the component's props if they are not immediately obvious. Keep in mind that in most cases, the docsExport field will be displayed within a container that is no more than 500-600px wide. The difference between these two fields is that the docsExport field will be displayed in the users command palette for quick reference, and the fullDocsExport will optionally link to an independent documentation page for more verbose documentation with a full screen layout to include demonstrations and examples.

Slot Components

Slot components are only slightly more complicated than embeddable components. To occupy a slot within the user's compiled application, your entire plugin must occupy that parent slot. This means that for any given plugin, you are only able to produce components for one subset of the compiled application. For example, if you want to build a plugin that replaces the navbar for users, your plugin config must satisfy the following values:

const pluginConfig: DeveloperConfigInput = {
    slot: "navigation",
    components: [
        {
            slot: "navbar",
            ...
        }
    ]
};

Styling your components

Provided Utilities

ULLD provides dozens of useful utilities for more quickly building your components, from pre-styled containers guaranteed to fit with the user's application to drop-in form components. Make sure to check out the UI utilities documentation here and the functional utilities like hooks and state helpers here

Since a user will likely include other plugins along side yours, it is important to maintain as strict of a policy around using css and tailwind variables to style your components as absolutely possible. This will ensure a uniform look and feel throughout the user's notes and application, regardless if the component is an embedded component or a slot component. Along side standard tailwind properties and colors, ULLD uses Shadcn based themes by default, and even if a user chooses to move away from a Shadcn based theme, the Shadcn color variables will remain available. You can review that documentation here.

Responsiveness

All mdx content will always be wrapped in a @container/mdx class applied through the @tailwindcss/container-queries tailwind plugin, and it is heavily suggested that you rely upon this class for most responsiveness issues and not the viewport or parent directly, unless your component triggers something absolutely positioned like modal.

Pages

Documentation in progress