@blinkk/editor
Version:
Structured content editor with live previews.
779 lines (778 loc) • 19.9 kB
TypeScript
import { EditorNotification } from './parts/notifications';
import { FeatureManagerSettings } from '../utility/featureManager';
import { FieldConfig } from '@blinkk/selective-edit';
import { IncludeExcludeFilterConfig } from '../utility/filter';
import { LiveEditorLabels } from './editor';
import bent from 'bent';
/**
* Interface for the live editor api.
*
* This defines how the editor works with the underlying data. The api
* is responsible for all file or network operations needed to make the
* editor function.
*/
export interface LiveEditorApiComponent {
/**
* Verify that the authentication for services that require auth.
*
* @returns True if the auth checks out.
*/
checkAuth(): boolean;
/**
* Copy a file.
*
* @param path Full path for the original file.
* @param path Full path for the new file.
*/
copyFile(originalPath: string, path: string): Promise<FileData>;
/**
* Create a new file from scratch.
*
* @param path Full path for the new file.
*/
createFile(path: string): Promise<FileData>;
/**
* Create a new workspace based off an existing workspace.
*
* @param base Workspace to base new workspace from.
* @param workspace New workspace name.
*/
createWorkspace(base: WorkspaceData, workspace: string): Promise<WorkspaceData>;
/**
* Delete an existing file.
*
* @param path Full path for the file being deleted.
*/
deleteFile(file: FileData): Promise<EmptyData>;
/**
* Retrieve the devices used for previews.
*/
getDevices(): Promise<Array<DeviceData>>;
/**
* Retrieve the file information.
*
* This is a complete loading of the file information and
* configuration for use in rendering the editor for the
* file.
*/
getFile(file: FileData): Promise<EditorFileData>;
/**
* Retrieve the files that can be edited in the editor.
*/
getFiles(): Promise<Array<FileData>>;
/**
* Retrieve the url to preview the file.
*
* When retrieving a list of files it is often slow
*/
getFileUrl(file: FileData): Promise<FileData>;
/**
* Retrieve information about the project.
*/
getProject(): Promise<ProjectData>;
/**
* Retrieve information about the active workspace.
*/
getWorkspace(): Promise<WorkspaceData>;
/**
* Retrieve information about available workspaces.
*/
getWorkspaces(): Promise<Array<WorkspaceData>>;
/**
* Load the workspace.
*
* This may redirect to a different URL.
* (ex: workspaces may be domain based.)
*/
loadWorkspace(workspace: WorkspaceData): Promise<WorkspaceData>;
/**
* Project type specific apis.
*/
projectTypes: ApiProjectTypes;
/**
* Start the publish process.
*
* Begins the publish process. Some publish processes may take time and cannot
* be completed right away. This api begins the process of publishing and
* gives a status response on the new publish.
*/
publish(workspace: WorkspaceData, data?: Record<string, any>): Promise<PublishResult>;
/**
* Save the updated file data.
*
* @param file File data to be saved.
* @param isRawEdit Is the edit to the raw file data?
*/
saveFile(file: EditorFileData, isRawEdit: boolean): Promise<EditorFileData>;
/**
* Upload a file.
*
* Uses a File object to provide a blob file that should be uploaded
* or saved appropriately. Often for media like images or videos.
*/
uploadFile(file: File, options?: MediaOptions): Promise<MediaFileData>;
}
export interface ApiProjectTypes {
amagaki: AmagakiProjectTypeApi;
grow: GrowProjectTypeApi;
}
export interface AmagakiProjectTypeApi {
/**
* Retrieve the partials for the project for the partials field.
*/
getPartials(): Promise<Record<string, PartialData>>;
}
export interface GrowProjectTypeApi {
/**
* Retrieve the partials for the project for the partials field.
*/
getPartials(): Promise<Record<string, PartialData>>;
/**
* Retrieve the available strings used in the `!g.string` yaml constructor.
*
* Returns a mapping of strings podpath to the contents of the strings file.
*
* ```json
* {
* "/content/strings/example.yaml": {
* "foo": "bar"
* }
* }
* ```
*/
getStrings(): Promise<Record<string, any>>;
}
/**
* Interface for the structure of the editor settings file.
*
* Settings in a project's `editor.yaml` should follow this interface.
*/
export interface EditorFileSettings {
/**
* Title of the site to display in the editor.
*/
title?: string;
/**
* Devices to use in the editor preview.
*/
devices?: Array<DeviceData>;
/**
* Editor experiment flags and settings.
*
* Used to control editor.dev experiments for the project.
*/
experiments?: Record<string, boolean | FeatureManagerSettings>;
/**
* Editor feature flags and settings.
*
* Used to control editor.dev features for the project.
*/
features?: Record<string, boolean | FeatureManagerSettings>;
/**
* Media configuration for the project.
*
* This controls options around how the media is handled in the project.
* Including custom providers for media upload.
*/
media?: ProjectMediaConfig;
/**
* Configuration for the site display in the editor.
*/
site?: SiteData;
/**
* Users or groups approved access to the editor.
*/
users?: Array<UserData>;
/**
* Settings for customizing the editor UI.
*/
ui?: EditorUiSettings;
}
export interface EditorUiSettings {
/**
* Labels for customizing the editor UI.
*/
labels?: LiveEditorLabels;
}
/**
* Device information used for previews.
*/
export interface DeviceData {
/**
* Can the device preview be rotated?
*/
canRotate?: boolean;
/**
* Height of the device view.
*/
height?: number;
/**
* Label for the device.
*/
label: string;
/**
* Width of the device view.
*/
width: number;
}
/**
* Full file information for rendering the file editor.
*/
export interface EditorFileData {
/**
* File contents.
*
* For example, the html in an html file, or the markdown body
* in a markdown file.
*/
content?: string;
/**
* File data.
*
* For example, the frontmatter for a markdown file or the contents
* of a yaml file.
*/
data?: any;
/**
* Raw file data.
*
* For example, the frontmatter for a markdown file or the contents
* of a yaml file. This is the unprocessed data string that can be edited
* in the 'Raw' content form.
*/
dataRaw?: string;
/**
* Editor configuration for the file.
*
* If not provided the editor will attempt to guess the fields to use.
*/
editor?: EditorFileConfig;
/**
* File information.
*/
file: FileData;
/**
* File repository history.
*/
history?: Array<RepoCommit>;
/**
* Sha of the file being edited.
*
* Used by the api to verify that there are no new changes to the file
* since the edit started to avoid overwriting external changes.
*/
sha?: string;
/**
* URL for viewing the file in the preview iframe.
*
* If no url is provided the preview will be hidden.
*/
url?: string;
/**
* URLs for viewing the file in different environments.
*/
urls?: Array<UrlConfig>;
}
/**
* Empty response from the api.
*/
export interface EmptyData {
}
/**
* File information.
*/
export interface FileData {
/**
* Complete path for the file.
*/
path: string;
/**
* URL for serving the file.
*
* Used for showing previews of different files.
* For example, image or video previews.
*
* For performance reasons, file data does not need to include url
* information as it may require more time to properly retrieve the
* url for a file which can slow down file list retrieval.
*
* `undefined` url is used to denote that the url was not retrieved.
* `null` url is used to denote that there is no url for the file.
*
* The editor will attempt to use the `getFileUrl()` api method when
* the url is `undefined` and the url is needed. If the value is `null`
* the editor assumes that there is no url for the file and does not
* make a request to the `getFileUrl()` method.
*/
url?: string | null;
}
export interface PartialData {
partial: string;
editor?: PartialEditorConfig;
}
/**
* Configuration for rendering the file editor.
*/
export interface PartialEditorConfig {
/**
* Partial label.
*/
label?: string;
/**
* Partial description.
*/
description?: string;
/**
* Field configurations for the editor.
*/
fields: Array<FieldConfig>;
/**
* Preview field keys.
*
* When showing a preview of the partial, use these field keys to determine
* the value to show for the preview.
*/
previewFields?: Array<string>;
}
/**
* Overall project information.
*/
export interface ProjectData {
/**
* Project title
*/
title: string;
/**
* Editor experiment flags and settings.
*
* Experiments can be defined in the `editor.yaml` file for the project.
* The API can override experiment flags if the API does not support
* specific experiments of the editor.
*/
experiments?: Record<string, boolean | FeatureManagerSettings>;
/**
* Editor feature flags and settings.
*
* Features can be defined in the `editor.yaml` file for the project.
* The API can override feature flags if the API does not support
* specific features of the editor.
*/
features?: Record<string, boolean | FeatureManagerSettings>;
/**
* Media configuration for the project.
*
* This controls options around how the media is handled in the project.
*/
media?: ProjectMediaConfig;
/**
* Publish configuration for the project.
*
* This controls if the UI allows for publishing and what information
* to collect for providing to the `publish` method on the api.
*/
publish?: ProjectPublishConfig;
/**
* Configuration for the site display.
*/
site?: SiteData;
/**
* Project type for the editor to use.
*/
type?: ProjectTypes | string;
/**
* Settings for customizing the editor UI.
*/
ui?: EditorUiSettings;
/**
* Users or groups approved access to the editor.
*/
users?: Array<UserData>;
}
/**
* Result from pinging the api.
*/
export interface PingResult {
/**
* Status of the api.
*/
status: PingStatus | string;
}
/**
* Result from starting the publish process.
*/
export interface PublishResult {
/**
* Status of the publish process.
*/
status: PublishStatus | string;
/**
* Updated workspace data.
*
* When a publish process is complete the workflow for the publish
* process can either keep the same workspace open, or remove it.
* In the case that the workspace is removed, the api can direct the
* editor to load a different workspace instead.
*/
workspace?: WorkspaceData;
/**
* URLs for viewing the publish state.
*/
urls?: Array<UrlConfig>;
}
/**
* Site information.
*/
export interface SiteData {
/**
* Site files configuration.
*/
files?: SiteFilesConfig;
}
/**
* User information.
*/
export interface UserData {
/**
* Name of the user.
*/
name: string;
/**
* Email representing the user.
*/
email: string;
/**
* Is the user data a group?
*/
isGroup?: boolean;
}
/**
* Workspace information.
*/
export interface WorkspaceData {
/**
* Full branch information from the workspace.
*/
branch: RepoBranch;
/**
* Short name for the workspace used in labels and lists.
*/
name: string;
/**
* Workspace specific publishing configuration.
*/
publish?: WorkspacePublishConfig;
}
/**
* When there are errors with an api call the promise should
* be rejected with an ApiError argument.
*
* ```typescript
* createWorkspace(base: WorkspaceData, workspace: string): Promise<null> {
* return new Promise<null>((resolve, reject) => {
* // Successful api calls resolve() when done.
* // Failure should reject() the promise with an ApiError argument.
* reject({message: 'Houston we have a problem'} as ApiError);
* });
* }
* ```
*/
export interface ApiError extends EditorNotification {
/**
* Additional meta information that can be used for a full report
* or debugging of the error.
*/
details?: any;
}
/**
* Catch and announce an api error.
*
* @param error Error from api.
*/
export declare function catchError(error: ApiError | bent.StatusError, callback?: (error: ApiError) => void): void;
/**
* Auxillary interfaces used in the api data.
*/
/**
* Configuration for rendering the file editor.
*/
export interface EditorFileConfig {
/**
* Field configurations for the editor.
*/
fields: Array<FieldConfig>;
}
/**
* Configuration for how media works in the editor UI.
*/
export interface ProjectMediaConfig {
/**
* Remote configuration for uploading media to a remote provider.
*
* This is used for things such as uploading to a CDN or
* optimization service.
*/
remote?: RemoteMediaOptions;
/**
* Local media configuration for uploading media using the
* connector api.
*/
options?: MediaOptions;
}
/**
* Configuration for how publishing works in the editor UI.
*/
export interface ProjectPublishConfig {
/**
* Field information for collecting information for the publish process.
*
* If there are field configurations provided the UI will prompt the user
* for the information and pass it on to the `publish` api call.
*/
fields?: Array<FieldConfig>;
}
/**
* Configuration for how site files are displayed.
*/
export interface SiteFilesConfig {
/**
* Filter settings for how the site files are filtered.
*
* By default the site files filters:
* - Only `.yaml`, `.md`, and `.html` files.
* - Ignores files and directories starting with `_` and `.`.
*/
filter?: IncludeExcludeFilterConfig;
}
/**
* Remote media providers supported in the editor.
*/
export declare enum RemoteMediaProviders {
GCS = "GCS"
}
/**
* Project types supported in the editor.
*/
export declare enum ProjectTypes {
Amagaki = "Amagaki",
Grow = "Grow"
}
/**
* Status for the api ping.
*/
export declare enum PingStatus {
/**
* Api is available.
*/
Ok = "Ok"
}
/**
* Status for the publish process.
*/
export declare enum PublishStatus {
/**
* There are no publish processes allowed.
*
* Some workspaces may not allow for publishing.
* For example the `main` branch has no where to be published.
*/
NotAllowed = "NotAllowed",
/**
* There are no active publish processes.
*/
NotStarted = "NotStarted",
/**
* There are no changes to publish.
*
* For example, the main branch and the current branch are on the same
* commit and there is nothing to publish.
*/
NoChanges = "NoChanges",
/**
* There is an active publish in process.
*/
Pending = "Pending",
/**
* The publish process has completed.
*/
Complete = "Complete",
/**
* There was a problem during the publish process.
*/
Failure = "Failure"
}
/**
* Repository author information.
*/
export interface RepoAuthor {
/**
* Name of the author.
*/
name: string;
/**
* Email address of the author.
*/
email: string;
}
/**
* Repository branch information.
*/
export interface RepoBranch {
/**
* Commit most recent commit.
*/
commit: RepoCommit;
/**
* Full branch name.
*/
name: string;
/**
* Url for viewing the branch in an external system.
*/
url?: string;
}
/**
* Repository commit information.
*/
export interface RepoCommit {
/**
* Author of the last commit.
*/
author?: RepoAuthor;
/**
* Commit hash of the last commit.
*/
hash: string;
/**
* Full commit message.
*/
message?: string;
/**
* Summary of the commit.
*/
summary?: string;
/**
* Timestamp of commit.
*
* Needs to be in a `Date.parse()` valid datetime format.
* For example: ISO 8601.
*/
timestamp?: string;
/**
* Url to view the commit externally.
*/
url?: string;
}
/**
* Configuration for url the file editor.
*/
export interface UrlConfig {
/**
* Label for the url.
*/
label: string;
/**
* Access level for the url.
*/
level: UrlLevel | string;
/**
* URL for viewing.
*/
url: string;
}
/**
* Editor url accessibility level for a resource.
*/
export declare enum UrlLevel {
/**
* Private url, should not be shared to others and not public.
*
* For example, the editor preview url. It should not be shared
* widely due to the transitive nature of workspaces, but can
* still be used to viewed when needed.
*/
Private = "Private",
/**
* Protected url, a shared service that is used for sharing
* but still restricted in how it is accessed.
*
* For example, a staging server to preview changes before
* they are live.
*/
Protected = "Protected",
/**
* Public url, a publicly accessbile way to access the resource.
*
* For example, the live version of the site that users normally
* see.
*/
Public = "Public",
/**
* Source url, a remotely hosted version of the resource.
*
* For example, a url that shows the resource in a repository
* like github.
*/
Source = "Source"
}
/**
* Configuration for how publishing works with a workspace.
*/
export interface WorkspacePublishConfig {
/**
* Current status of the workspace publishing.
*/
status: PublishStatus | string;
/**
* URLs for viewing the publish state.
*/
urls?: Array<UrlConfig>;
}
/**
* Media interfaces.
*/
/**
* Metadata from a remote provider media upload.
*/
export interface MediaFileMetaInfo {
height?: number;
mimeType?: string;
width?: number;
}
/**
* File data specific to media files.
*/
export interface MediaFileData extends FileData {
meta?: MediaFileMetaInfo;
}
/**
* Configuration for remote media providers.
*
* Used by the file upload to support different services to upload the
* media to be processed/stored.
*/
export interface MediaOptions {
/**
* Identifier for the provider that will be handling the upload
* request.
*/
provider?: RemoteMediaProviders | string;
}
/**
* Configuration for the Google remote media provider.
*
* Currently works with the backend from https://github.com/grow/grow-ext-google-cloud-images
*/
export interface GoogleMediaOptions extends MediaOptions {
/**
* Url for the endpoint to handle the file upload.
*/
url: string;
/**
* Bucket name to upload the media file to.
*/
bucket?: string;
}
/**
* Supported options for remote media provider options.
*/
export declare type RemoteMediaOptions = GoogleMediaOptions;