mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +00:00 
			
		
		
		
	(core) configure typedoc for generating plugin api documentation
Summary: This annotates the plugin api sufficiently to generate some documentation for it. See https://github.com/gristlabs/grist-help/pull/139 Contains some small code tweaks for things that caused typedoc some trouble. Test Plan: manual inspection of output Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3342
This commit is contained in:
		
							parent
							
								
									d8af25de9d
								
							
						
					
					
						commit
						c6d66e15bf
					
				| @ -69,40 +69,58 @@ export interface GristAPI { | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * GristDocAPI interface is implemented by Grist, and allows getting information from and | ||||
|  * interacting with the Grist document to which a plugin is attached. | ||||
|  * Allows getting information from and nteracting with the Grist document to which a plugin or widget is attached. | ||||
|  */ | ||||
| export interface GristDocAPI { | ||||
|   // Returns the docName that identifies the document.
 | ||||
|   /** | ||||
|    * Returns an identifier for the document. | ||||
|    */ | ||||
|   getDocName(): Promise<string>; | ||||
| 
 | ||||
|   // Returns a sorted list of table IDs.
 | ||||
|   /** | ||||
|    * Returns a sorted list of table IDs. | ||||
|    */ | ||||
|   listTables(): Promise<string[]>; | ||||
| 
 | ||||
|   // Returns a complete table of data in the format {colId: [values]}, including the 'id' column.
 | ||||
|   // Do not modify the returned arrays in-place, especially if used directly (not over RPC).
 | ||||
|   // TODO: return type is Promise{[colId: string]: CellValue[]}> but cannot be specified because
 | ||||
|   // ts-interface-builder does not properly support index-signature.
 | ||||
|   /** | ||||
|    * Returns a complete table of data in the format {colId: [values]}, including the 'id' column. | ||||
|    * Do not modify the returned arrays in-place, especially if used directly (not over RPC). | ||||
|    * TODO: return type is Promise{[colId: string]: CellValue[]}> but cannot be specified because | ||||
|    * ts-interface-builder does not properly support index-signature. | ||||
|    */ | ||||
|   fetchTable(tableId: string): Promise<any>; | ||||
| 
 | ||||
|   // Applies an array of user actions.
 | ||||
|   // todo: return type should be Promise<ApplyUAResult>, but this requires importing modules from
 | ||||
|   // `app/common` which is not currently supported by the build.
 | ||||
|   /** | ||||
|    * Applies an array of user actions. | ||||
|    * TODO: return type should be Promise<ApplyUAResult>, but this requires importing modules from | ||||
|    * `app/common` which is not currently supported by the build. | ||||
|    */ | ||||
|   applyUserActions(actions: any[][], options?: any): Promise<any>; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Interface for the data backing a single widget. | ||||
|  */ | ||||
| export interface GristView { | ||||
|   // Like fetchTable, but gets data for the custom section specifically, if there is any.
 | ||||
|   // TODO: return type is Promise{[colId: string]: CellValue[]}> but cannot be specified because
 | ||||
|   // ts-interface-builder does not properly support index-signature.
 | ||||
|   /** | ||||
|    * Like [[GristDocAPI.fetchTable]], but gets data for the custom section specifically, if there is any. | ||||
|    * TODO: return type is Promise{[colId: string]: CellValue[]}> but cannot be specified because | ||||
|    * ts-interface-builder does not properly support index-signature. | ||||
|    */ | ||||
|   fetchSelectedTable(): Promise<any>; | ||||
| 
 | ||||
|   // Similar TODO to fetchSelectedTable for return type.
 | ||||
|   /** | ||||
|    * Similar TODO to `fetchSelectedTable()` for return type. | ||||
|    */ | ||||
|   fetchSelectedRecord(rowId: number): Promise<any>; | ||||
| 
 | ||||
|   // Allow custom widget to be listed as a possible source for linking with SELECT BY.
 | ||||
|   /** | ||||
|    * Allow custom widget to be listed as a possible source for linking with SELECT BY. | ||||
|    */ | ||||
|   allowSelectBy(): Promise<void>; | ||||
| 
 | ||||
|   // Set the list of selected rows to be used against any linked widget. Requires `allowSelectBy()`.
 | ||||
|   /** | ||||
|    * Set the list of selected rows to be used against any linked widget. Requires `allowSelectBy()`. | ||||
|    */ | ||||
|   setSelectedRows(rowIds: number[]): Promise<void>; | ||||
| } | ||||
|  | ||||
							
								
								
									
										1
									
								
								app/plugin/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								app/plugin/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| Methods here are available for use in Grist custom widgets. | ||||
| @ -1,5 +1,7 @@ | ||||
| /** | ||||
|  * Where to append the content that a plugin renders. | ||||
|  * | ||||
|  * @internal | ||||
|  */ | ||||
| export type RenderTarget = "fullscreen" | number; | ||||
| 
 | ||||
|  | ||||
| @ -4,22 +4,32 @@ import * as Types from 'app/plugin/DocApiTypes'; | ||||
|  * Offer CRUD-style operations on a table. | ||||
|  */ | ||||
| export interface TableOperations { | ||||
|   // Create a record or records.
 | ||||
|   /** | ||||
|    * Create a record or records. | ||||
|    */ | ||||
|   create(records: Types.NewRecord, options?: OpOptions): Promise<Types.MinimalRecord>; | ||||
|   create(records: Types.NewRecord[], options?: OpOptions): Promise<Types.MinimalRecord[]>; | ||||
| 
 | ||||
|   // Update a record or records.
 | ||||
|   /** | ||||
|    * Update a record or records. | ||||
|    */ | ||||
|   update(records: Types.Record|Types.Record[], options?: OpOptions): Promise<void>; | ||||
| 
 | ||||
|   // Delete a record or records.
 | ||||
|   /** | ||||
|    * Delete a record or records. | ||||
|    */ | ||||
|   destroy(recordId: Types.RecordId): Promise<Types.RecordId>; | ||||
|   destroy(recordIds: Types.RecordId[]): Promise<Types.RecordId[]>; | ||||
| 
 | ||||
|   // Add or update a record or records.
 | ||||
|   /** | ||||
|    * Add or update a record or records. | ||||
|    */ | ||||
|   upsert(records: Types.AddOrUpdateRecord|Types.AddOrUpdateRecord[], | ||||
|          options?: UpsertOptions): Promise<void>; | ||||
| 
 | ||||
|   // Determine the tableId of the table.
 | ||||
|   /** | ||||
|    * Determine the tableId of the table. | ||||
|    */ | ||||
|   getTableId(): Promise<string>; | ||||
| 
 | ||||
|   // TODO: offer a way to query the table.
 | ||||
| @ -32,7 +42,7 @@ export interface TableOperations { | ||||
|  * This can be disabled. | ||||
|  */ | ||||
| export interface OpOptions { | ||||
|   parseStrings?: boolean; | ||||
|   parseStrings?: boolean;  /** whether to parse strings based on the column type. */ | ||||
| } | ||||
| 
 | ||||
| /** | ||||
| @ -40,8 +50,8 @@ export interface OpOptions { | ||||
|  * onMany is first, and allowEmptyRequire is false. | ||||
|  */ | ||||
| export interface UpsertOptions extends OpOptions { | ||||
|   add?: boolean;      // permit inserting a record
 | ||||
|   update?: boolean;   // permit updating a record
 | ||||
|   onMany?: 'none' | 'first' | 'all';  // whether to update none, one, or all matching records
 | ||||
|   allowEmptyRequire?: boolean; // allow "wildcard" operation
 | ||||
|   add?: boolean;      /** permit inserting a record */ | ||||
|   update?: boolean;   /** permit updating a record */ | ||||
|   onMany?: 'none' | 'first' | 'all';  /** whether to update none, one, or all matching records */ | ||||
|   allowEmptyRequire?: boolean; /** allow "wildcard" operation */ | ||||
| } | ||||
|  | ||||
| @ -46,46 +46,103 @@ export const rpc: Rpc = new Rpc({logger: createRpcLogger()}); | ||||
| 
 | ||||
| export const api = rpc.getStub<GristAPI>(RPC_GRISTAPI_INTERFACE, checkers.GristAPI); | ||||
| export const coreDocApi = rpc.getStub<GristDocAPI>('GristDocAPI@grist', checkers.GristDocAPI); | ||||
| 
 | ||||
| /** | ||||
|  * Interface for the records backing a custom widget. | ||||
|  */ | ||||
| export const viewApi = rpc.getStub<GristView>('GristView', checkers.GristView); | ||||
| 
 | ||||
| /** | ||||
|  * Interface for the state of a custom widget. | ||||
|  */ | ||||
| export const widgetApi = rpc.getStub<WidgetAPI>('WidgetAPI', checkers.WidgetAPI); | ||||
| 
 | ||||
| /** | ||||
|  * Interface for the mapping of a custom widget. | ||||
|  */ | ||||
| export const sectionApi = rpc.getStub<CustomSectionAPI>('CustomSectionAPI', checkers.CustomSectionAPI); | ||||
| 
 | ||||
| /** | ||||
|  * Shortcut for [[GristView.allowSelectBy]]. | ||||
|  */ | ||||
| export const allowSelectBy = viewApi.allowSelectBy; | ||||
| 
 | ||||
| /** | ||||
|  * Shortcut for [[GristView.setSelectedRows]]. | ||||
|  */ | ||||
| export const setSelectedRows = viewApi.setSelectedRows; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Fetches data backing the widget as for [[GristView.fetchSelectedTable]], | ||||
|  * but decoding data by default, replacing e.g. ['D', timestamp] with | ||||
|  * a moment date. Option `keepEncoded` skips the decoding step. | ||||
|  */ | ||||
| export async function fetchSelectedTable(options: {keepEncoded?: boolean} = {}) { | ||||
|   const table = await viewApi.fetchSelectedTable(); | ||||
|   return options.keepEncoded ? table : | ||||
|     mapValues<any[], any[]>(table, (col) => col.map(decodeObject)); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Fetches current selected record as for [[GristView.fetchSelectedRecord]], | ||||
|  * but decoding data by default, replacing e.g. ['D', timestamp] with | ||||
|  * a moment date. Option `keepEncoded` skips the decoding step. | ||||
|  */ | ||||
| export async function fetchSelectedRecord(rowId: number, options: {keepEncoded?: boolean} = {}) { | ||||
|   const rec = await viewApi.fetchSelectedRecord(rowId); | ||||
|   return options.keepEncoded ? rec : | ||||
|     mapValues(rec, decodeObject); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * A collection of methods for fetching document data. The | ||||
|  * fetchSelectedTable and fetchSelectedRecord methods are | ||||
|  * overridden to decode data by default. | ||||
|  */ | ||||
| export const docApi: GristDocAPI & GristView = { | ||||
|   ...coreDocApi, | ||||
|   ...viewApi, | ||||
| 
 | ||||
|   // Change fetchSelectedTable() to decode data by default, replacing e.g. ['D', timestamp] with
 | ||||
|   // a moment date. New option `keepEncoded` skips the decoding step.
 | ||||
|   async fetchSelectedTable(options: {keepEncoded?: boolean} = {}) { | ||||
|     const table = await viewApi.fetchSelectedTable(); | ||||
|     return options.keepEncoded ? table : | ||||
|       mapValues<any[], any[]>(table, (col) => col.map(decodeObject)); | ||||
|   }, | ||||
| 
 | ||||
|   // Change fetchSelectedRecord() to decode data by default, replacing e.g. ['D', timestamp] with
 | ||||
|   // a moment date. New option `keepEncoded` skips the decoding step.
 | ||||
|   async fetchSelectedRecord(rowId: number, options: {keepEncoded?: boolean} = {}) { | ||||
|     const rec = await viewApi.fetchSelectedRecord(rowId); | ||||
|     return options.keepEncoded ? rec : | ||||
|       mapValues(rec, decodeObject); | ||||
|   }, | ||||
|   fetchSelectedTable, | ||||
|   fetchSelectedRecord, | ||||
| }; | ||||
| 
 | ||||
| export const on = rpc.on.bind(rpc); | ||||
| 
 | ||||
| // Exposing widgetApi methods in a module scope.
 | ||||
| 
 | ||||
| /** | ||||
|  * Shortcut for [[WidgetAPI.getOption]] | ||||
|  */ | ||||
| export const getOption = widgetApi.getOption.bind(widgetApi); | ||||
| 
 | ||||
| /** | ||||
|  * Shortcut for [[WidgetAPI.setOption]] | ||||
|  */ | ||||
| export const setOption = widgetApi.setOption.bind(widgetApi); | ||||
| 
 | ||||
| /** | ||||
|  * Shortcut for [[WidgetAPI.setOptions]] | ||||
|  */ | ||||
| export const setOptions = widgetApi.setOptions.bind(widgetApi); | ||||
| 
 | ||||
| /** | ||||
|  * Shortcut for [[WidgetAPI.getOptions]] | ||||
|  */ | ||||
| export const getOptions = widgetApi.getOptions.bind(widgetApi); | ||||
| 
 | ||||
| /** | ||||
|  * Shortcut for [[WidgetAPI.clearOptions]] | ||||
|  */ | ||||
| export const clearOptions = widgetApi.clearOptions.bind(widgetApi); | ||||
| 
 | ||||
| // Get access to a table in the document. If no tableId specified, this
 | ||||
| // will use the current selected table (for custom widgets).
 | ||||
| // If a table does not exist, there will be no error until an operation
 | ||||
| // on the table is attempted.
 | ||||
| /** | ||||
|  * Get access to a table in the document. If no tableId specified, this | ||||
|  * will use the current selected table (for custom widgets). | ||||
|  * If a table does not exist, there will be no error until an operation | ||||
|  * on the table is attempted. | ||||
|  */ | ||||
| export function getTable(tableId?: string): TableOperations { | ||||
|   return new TableOperationsImpl({ | ||||
|     async getTableId() { | ||||
| @ -100,7 +157,9 @@ export function getTable(tableId?: string): TableOperations { | ||||
|   }, {}); | ||||
| } | ||||
| 
 | ||||
| // Get the current selected table (for custom widgets).
 | ||||
| /** | ||||
|  * Get the current selected table (for custom widgets). | ||||
|  */ | ||||
| export const selectedTable: TableOperations = getTable(); | ||||
| 
 | ||||
| // Get the ID of the current selected table (for custom widgets).
 | ||||
| @ -240,13 +299,15 @@ export function mapColumnNamesBack(data: any, options: { | ||||
|   return mapColumnNames(data, {...options, reverse: true}); | ||||
| } | ||||
| 
 | ||||
| // For custom widgets, add a handler that will be called whenever the
 | ||||
| // row with the cursor changes - either by switching to a different row, or
 | ||||
| // by some value within the row potentially changing.  Handler may
 | ||||
| // in the future be called with null if the cursor moves away from
 | ||||
| // any row.
 | ||||
| // TODO: currently this will be called even if the content of a different row
 | ||||
| // changes.
 | ||||
| /** | ||||
|  * For custom widgets, add a handler that will be called whenever the | ||||
|  * row with the cursor changes - either by switching to a different row, or | ||||
|  * by some value within the row potentially changing.  Handler may | ||||
|  * in the future be called with null if the cursor moves away from | ||||
|  * any row. | ||||
|  * TODO: currently this will be called even if the content of a different row | ||||
|  * changes. | ||||
|  */ | ||||
| export function onRecord(callback: (data: RowRecord | null, mappings: WidgetColumnMap | null) => unknown) { | ||||
|   on('message', async function(msg) { | ||||
|     if (!msg.tableId || !msg.rowId) { return; } | ||||
| @ -254,8 +315,11 @@ export function onRecord(callback: (data: RowRecord | null, mappings: WidgetColu | ||||
|     callback(rec, await getMappingsIfChanged(msg)); | ||||
|   }); | ||||
| } | ||||
| // For custom widgets, add a handler that will be called whenever the
 | ||||
| // selected records change.  Handler will be called with a list of records.
 | ||||
| 
 | ||||
| /** | ||||
|  * For custom widgets, add a handler that will be called whenever the | ||||
|  * selected records change.  Handler will be called with a list of records. | ||||
|  */ | ||||
| export function onRecords(callback: (data: RowRecord[], mappings: WidgetColumnMap | null) => unknown) { | ||||
|   on('message', async function(msg) { | ||||
|     if (!msg.tableId || !msg.dataChange) { return; } | ||||
| @ -274,10 +338,13 @@ export function onRecords(callback: (data: RowRecord[], mappings: WidgetColumnMa | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // For custom widgets, add a handler that will be called whenever the
 | ||||
| // widget options change (and on initial ready message). Handler will be
 | ||||
| // called with an object containing save json options, or null if no options were saved.
 | ||||
| // Second parameter
 | ||||
| /** | ||||
|  * For custom widgets, add a handler that will be called whenever the | ||||
|  * widget options change (and on initial ready message). Handler will be | ||||
|  * called with an object containing saved json options, or null if no options were saved. | ||||
|  * The second parameter has information about the widgets relationship with | ||||
|  * the document that contains it. | ||||
|  */ | ||||
| export function onOptions(callback: (options: any, settings: InteractionOptions) => unknown) { | ||||
|   on('message', async function(msg) { | ||||
|     if (msg.settings) { | ||||
| @ -297,6 +364,7 @@ export function onOptions(callback: (options: any, settings: InteractionOptions) | ||||
|  * `name`. Calling `addImporter(...)` from another component than a `safeBrowser` component is not | ||||
|  * currently supported. | ||||
|  * | ||||
|  * @internal | ||||
|  */ | ||||
| export async function addImporter(name: string, path: string, mode: 'fullscreen' | 'inline', options?: RenderOptions) { | ||||
|   // checker is omitted for implementation because call was already checked by grist.
 | ||||
| @ -315,7 +383,10 @@ export async function addImporter(name: string, path: string, mode: 'fullscreen' | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| interface ReadyPayload extends Omit<InteractionOptionsRequest, "hasCustomOptions"> { | ||||
| /** | ||||
|  * Options when initializing connection to Grist. | ||||
|  */ | ||||
| export interface ReadyPayload extends Omit<InteractionOptionsRequest, "hasCustomOptions"> { | ||||
|   /** | ||||
|    * Handler that will be called by Grist to open additional configuration panel inside the Custom Widget. | ||||
|    */ | ||||
| @ -354,6 +425,7 @@ export function ready(settings?: ReadyPayload): void { | ||||
|   })(); | ||||
| } | ||||
| 
 | ||||
| /** @internal */ | ||||
| function getPluginPath(location: Location) { | ||||
|   return location.pathname.replace(/^\/plugins\//, ''); | ||||
| } | ||||
| @ -393,6 +465,7 @@ if (typeof window !== 'undefined') { | ||||
|   rpc.setSendMessage((data) => { return; }); | ||||
| } | ||||
| 
 | ||||
| /** @internal */ | ||||
| function createRpcLogger(): IRpcLogger { | ||||
|   let prefix: string; | ||||
|   if (typeof window !== 'undefined') { | ||||
|  | ||||
| @ -90,7 +90,7 @@ export class TestServerMerged implements IMochaServer { | ||||
|     // logging. Server code uses a global logger, so it's hard to separate out (especially so if
 | ||||
|     // we ever run different servers for different tests).
 | ||||
|     const serverLog = process.env.VERBOSE ? 'inherit' : nodeLogFd; | ||||
|     const env = { | ||||
|     const env: Record<string, string> = { | ||||
|       TYPEORM_DATABASE: this._getDatabaseFile(), | ||||
|       TEST_CLEAN_DATABASE: reset ? 'true' : '', | ||||
|       GRIST_DATA_DIR: this.testDocDir, | ||||
|  | ||||
| @ -70,7 +70,7 @@ export class GristClient { | ||||
|       if (this._pending.length) { | ||||
|         return this._pending.shift(); | ||||
|       } | ||||
|       await new Promise(resolve => this._consumer = resolve); | ||||
|       await new Promise<void>(resolve => this._consumer = resolve); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -6,5 +6,14 @@ | ||||
|     { "path": "./app" }, | ||||
|     { "path": "./stubs/app" }, | ||||
|     { "path": "./test" }, | ||||
|   ] | ||||
|   ], | ||||
|   "typedocOptions": { | ||||
|     "entryPoints": [ | ||||
|       "app/plugin/grist-plugin-api.ts", | ||||
|       "app/plugin/TableOperations.ts", | ||||
|     ], | ||||
|     "excludeInternal": "true", | ||||
|     "excludeNotDocumented": "true", | ||||
|     "out": "doc" | ||||
|   } | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user