npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@tinalabs/tinacms-studio

v0.1.2

Published

> TODO: description

Downloads

5

Readme

tinacms-studio

Architecture

Stand-alone Admin

Tina Studio enables a stand-alone admin experience by managing the creation and setup of the TinaCMS React Microfrontend for the user using a vanilla JS API, and providing web standards API(s) for selecting entities to edit.

We've prototyped doing this by adding a mount method to the CMS and using basic query param routing:

const tinaConfig = require('./tina.config')
const cms = new TinaStudio(tinaConfig)

cms.mount({ routing: true })

Mount takes the following configuration options:

type MountOptions = {
  /* DOM element to mount the CMS to. Will create an element if omitted */
  el?: Element;
  
  /* Enable query param routing. Defaults to false */
  routing?: boolean;

  /* Render a client-side app in the admin using JSX */
  render?: (window?: Window, document?: Document) => ReactNode;
}

Routing is explained in Entities.

Browser Bundle

Currently TinaCMS is built as a UMD library. This means it cannot be used in the browser as-is. To work around this, we forked @tinacms/scripts as tinacms-plugin-utils and updated it to have more control over console output using ink.js and added support for building a UMD bundle, IIFE (Immediately Invoked Function Expression) browser bundle that mounts named exports to the window, and a ES module if desired for modern browsers, and packages direct dependencies inside it at build time, enabling usage in the browser.

This package is built with this tool, and exports the following:

  • dist/index.js: a UMD bundle that extends TinaCMS with prototypes of the functionality outlined in this RFC
  • dist/index.browser.js: an IIFE that adds TinaCMS and Form under the tina namespace

This allows us to include the browser bundle inside tools like Hugo and 11ty and use Tina off of the window:

<script src="https://unpkg.com/browse/[email protected]/umd/react.production.min.js"></script>
<script src="https://unpkg.com/browse/[email protected]/umd/react-dom.production.min.js"></script>
<script src="/tina-studio/index.browser.js"></script>
<script>
  const { TinaCMS, Form } = tina || window.tina;

  const cms = new TinaCMS({ ... })
  const form = new Form({ ... })

  cms.plugins.add(form)
  cms.mount({ routing: true })
</script>

Entities

Entities make TinaCMS aware of the content models of your content sources, such as Tina Cloud, Github, or other CMSes and databases.

Configuration

The following options are available for entities:

type EntityOptions {
  /** Provide a unique name/id for the entity */
  name: string;
  
  /** Single for a single document, list for an array, or a graph representing a hierarchy */
  type: "single" | "list" | "hierarchy"
  
  /** The schema/base form template for this entity */
  template: FormOptions<EntityShape> | ((record: EntityRecord, cms: TinaStudio) => FormOptions<EntityShape>)

  /** Provide the records to include, or a function that resolves them */
  include: EntityRecord | EntityRecord[] | (() => EntityRecord | EntityRecord[]);

  /** Provide instructions for exlcuding the records returned by `include` */
  exclude?: (record: EntityRecord, cms: TinaStudio) => boolean

  /** Override behaviour for finding a record by its id. Default behaviour is to match an `_id` property. */
  find?: (id: string, record: EntityRecord) => boolean

  /** Instructions for previewing this entity. Optional. */
  preview?: Preview | ((record: EntityRecord, cms: TinaStudio) => Promise<Preview>
}

You can add entities to the CMS config when creating the CMS:

const homepage = require('./content/homepage.json')

const cms = new TinaStudio({
  entities: [
    {
      name: "homepage",
      type: "single",
      include: homepage
    }
  ]
})

Or you can add them on-the-fly:

cms.api.github.getFile("./homepage.json")
  .then(data => {
    cms.entities.add({
        name: "homepage",
      type: "single",
      include: homepage  
    }))
  })
  .catch(error => console.error(error))

Using Entities

Entities can be queried from the CMS after adding them:

const postEntity = cms.entities.find('post');

Which returns the following:

type EntityState = {
  __type: "entity";
  name: string;
  
  /** Indicates the type of entity */
  type: "single" | "list" | "hierarchy";

  /** The base form template */
  template: FormOptions<EntityShape>;

  /** Returns all records */
  all: () => Type extends "single" ? Promise<EntityRecord> : 

  /** Find a specific record by id */
  find: (id: string) => Promise<EntityRecord | undefined>

  /** Fetch the preview for a given record */
  preview?: (id: string) => void
}

You can request all records for an entity:

const postEntity = cms.entities.find('post');
const posts = postEntity.all()
  .then(posts => console.log(posts));

You can also requst a specific record by id:

const postEntity = cms.entities.find('post');
const post = postEntitiy.find('example-id')
  .then(post => console.log(post));

Records also have their form template and a prepopulated form with their values:

const postEntity = cms.entities.find('post');
const post = postEntitiy.find('example-id')
  .then(post => {
    const { _template, _form } = post;
    const customForm = new Form({ 
      ..._template,
      label: "Custom Post Form",
      onChange: (values) => console.log(values)
    })

    cms.plugins.add(_form)
    cms.plugins.add(customForm)
  });

Routing

Entities can be browsed by query param when routing is set to true in the cms.mount arguments.

  • A single entity can be loaded by name: /admin?entity=homepage
  • Multiple entities can be loaded by name: /admin?entities=header,homepage,footer
  • For entities that represent lists or hierarchies, and additional param is requiered matching the entities name, equal to the unqiue ID of the record: /admin?entities=post&post=example-id

This is a very rough example currently. Some considerations for a production use case:

  • Only one preview should be loaded at a time; we need to handle when two entities with preview logic are select
    • Perhaps "first entity wins" or throw an error?
  • The CMS config does not support async logic for including entity records; this API should be revised.

Previews

Create an integration with the TinaCMS to enable a preview frame to display loading messages while previews are being fetched, display new previews when they are ready, and display errors to editors when fetching fails with the information developers will need to debug.

Entity Previews

Preview configuration can be added to entity configurations:

cms.entities.add({
  name: "homepage",
  type: "single",
  include: homepage,
  preview: (record, cms) => {
    const src = `http://example.com/${record.slug}`;

    return {
      src
    }
  }
})

And then enabled in code:

const homepageEntity = cms.entities.find("homepage")

homepageEntity.preview()

Remote Rendering

For tools that only support build-time rendering of pages, you can get the URL for the page hosted elsewhere and render that page in the preview frame each time a form is saved:

import { createPreview, STUDIO_PREVIEW_LOADING } from 'tinacms-studio';

const useMyPreview = (recordId: string) = createPreview(cms, async () => {
  try {
    const src = `http://example.com/${recordId}`;

    cms.events.dispatch({
      type: STUDIO_PREVIEW_LOADING,
      message: "Fetching preview..."
    })

    return {
      src
    }
  } catch (error) {
    return {
      error
    }
  }
})

Server-side Rendering

For tools that only support server-side rendering of pages, you can get the HTML for the page and render that HTML in the preview frame each time a form is saved:

Fetch remote HTML for a record and render it in the iframe:

import { createPreview, STUDIO_PREVIEW_LOADING } from 'tinacms-studio';

const useMyPreview = createPreview(cms, async () => {
  try {
    const src = `http://example.com/api/${recordId}`;

    cms.events.dispatch({
      type: STUDIO_PREVIEW_LOADING,
      message: "Fetching preview..."
    })

    const res = await fetch(src);
    const html = await res.text();

    return {
      html
    }
  } catch (error) {
    return {
      error
    }
  }
})

Miscellenaeous

Due to time constraints, we did not have time to alter the existing TinaCMS UI (sidebar, toolbar) to support this experience. Due to this, we rapidly prototyped a new UI with a terrible name currently of fullscreen.

This UI provides a split-pane experience closer to the ForestryCMS UX, which allowed us to rapidly prototype outcomes.

We suggest the feedback from usability testing is explored to decide how to integrate this functionality and develop a better TinaCMS UI.

export type FullscreenOptions {
  overlay: boolean; // Render as a floating overlay instead of a fullscreen SPA
  toggle: boolean; // Render a Tina branded floating action button to enable the CMS
  mode: "editor" | "studio"
}

This is configured on the CMS configuration:

const cms = new TinaCMS({
  fullscreen: {
    overlay: false,
    toggle: true,
    mode: "editor"
  }
})

NOTE

The studio mode was intended to be a much more Forestry-like experience, with a "entity" browser and document list. We did not have time to complete this experiment.

The fullscreen UI works by applying CSS styles to other CMS UIs using fuzzy CSS selectors. This is very fragile and is not production ready.

Contributing

Prerequisites

You must have NodeJS LTS installed.

Installation

Install the project dependencies by running:

npm install

Development

Start a development server by running:

npm start

Production Builds

Build a production build by running:

npm run build