lucid-extension-sdk 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lucid-extension-sdk",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "description": "Utility classes for writing Lucid Software editor extensions",
5
5
  "main": "sdk/index.js",
6
6
  "types": "sdk/index.d.ts",
@@ -20,8 +20,10 @@ export declare const enum CommandName {
20
20
  AddLineTextArea = "alta",
21
21
  AddMenuItem = "ami",
22
22
  AddShapeData = "asd",
23
+ Alert = "a",
23
24
  AnimateViewport = "av",
24
25
  Bootstrap = "b",
26
+ Confirm = "c",
25
27
  CreateBlock = "cb",
26
28
  CreateCollection = "cc",
27
29
  CreateDataSource = "cds",
@@ -100,6 +102,10 @@ export declare type CommandArgs = {
100
102
  query: AddShapeDataQuery;
101
103
  result: AddShapeDataResult;
102
104
  };
105
+ [CommandName.Alert]: {
106
+ query: AlertQuery;
107
+ result: AlertResult;
108
+ };
103
109
  [CommandName.AnimateViewport]: {
104
110
  query: AnimateViewportQuery;
105
111
  result: AnimateViewportResult;
@@ -108,6 +114,10 @@ export declare type CommandArgs = {
108
114
  query: BootstrapQuery;
109
115
  result: BootstrapResult;
110
116
  };
117
+ [CommandName.Confirm]: {
118
+ query: ConfirmQuery;
119
+ result: ConfirmResult;
120
+ };
111
121
  [CommandName.CreateBlock]: {
112
122
  query: CreateBlockQuery;
113
123
  result: CreateBlockResult;
@@ -361,7 +371,7 @@ export declare type AddMenuItemQuery = {
361
371
  /** Label to display on the menu item */
362
372
  'l': string;
363
373
  /** Named action to run when the menu item is clicked */
364
- 'a': string;
374
+ 'a'?: string;
365
375
  /** Named action that returns whether the menu item should be visible */
366
376
  'v'?: string;
367
377
  /** Named action that returns whether the menu item should be disabled */
@@ -370,6 +380,17 @@ export declare type AddMenuItemQuery = {
370
380
  't': MenuType;
371
381
  /** If specified, where to place the menu item; defaults to MenuLocation.Extension */
372
382
  'loc'?: MenuLocation;
383
+ /** If specified, this menu item should launch a file picker */
384
+ 'f'?: {
385
+ /** Action to call when files are selected */
386
+ 'a': string;
387
+ /** File types to accept */
388
+ 'ac': string;
389
+ /** Single file only */
390
+ 's'?: boolean;
391
+ /** Return content in a binary ArrayBuffer instead of as text */
392
+ 'b'?: boolean;
393
+ };
373
394
  };
374
395
  export declare type AddMenuItemResult = undefined;
375
396
  export declare type AddShapeDataQuery = {
@@ -383,6 +404,16 @@ export declare type AddShapeDataQuery = {
383
404
  'v'?: SerializedFieldType;
384
405
  };
385
406
  export declare type AddShapeDataResult = undefined;
407
+ export declare type AlertQuery = {
408
+ /** Title; defaults to extension title */
409
+ 't'?: string;
410
+ /** Body text */
411
+ 'b': string;
412
+ /** Button text; defaults to i18n'ed "OK" */
413
+ 'bt'?: string;
414
+ };
415
+ /** True if they click OK, false otherwise */
416
+ export declare type AlertResult = Promise<boolean>;
386
417
  export declare type BootstrapQuery = {
387
418
  /**
388
419
  * Named action that accepts the bootstrap data, and which may return a Promise or void. After awaiting\
@@ -391,6 +422,18 @@ export declare type BootstrapQuery = {
391
422
  'c': string;
392
423
  };
393
424
  export declare type BootstrapResult = Promise<void>;
425
+ export declare type ConfirmQuery = {
426
+ /** Title; defaults to extension title */
427
+ 't'?: string;
428
+ /** Body text */
429
+ 'b': string;
430
+ /** OK button text; defaults to i18n'ed "OK" */
431
+ 'o'?: string;
432
+ /** Cancel button text; defaults to i18n'ed "Cancel" */
433
+ 'c'?: string;
434
+ };
435
+ /** True if they click OK, false otherwise */
436
+ export declare type ConfirmResult = Promise<boolean>;
394
437
  export declare type CreateBlockQuery = {
395
438
  /**
396
439
  * By default, we add the block to the current page & currently active group. If specified, the
@@ -0,0 +1 @@
1
+ export declare function decodeBase64(base64String: string): Uint8Array;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decodeBase64 = void 0;
4
+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
5
+ //This gives a ~8x speedup vs. using alphabet.indexOf() in the main decode loop.
6
+ const charCodeToEncodedInt = [];
7
+ for (let i = 0; i < alphabet.length; i++) {
8
+ charCodeToEncodedInt[alphabet.charCodeAt(i)] = i;
9
+ }
10
+ function decodeBase64(base64String) {
11
+ //Filter out whitespace, etc
12
+ base64String = base64String.replace(/[^A-Za-z0-9\+\/\=]/g, '');
13
+ if (base64String.length % 4 !== 0) {
14
+ throw new Error('Invalid base64 input; length must be a multiple of 4');
15
+ }
16
+ //4 characters of input turns into 3 bytes of output
17
+ let bytes = (base64String.length / 4) * 3;
18
+ //But each trailing = (up to 2) is one fewer byte of output
19
+ if (base64String.endsWith('==')) {
20
+ bytes -= 2;
21
+ }
22
+ else if (base64String.endsWith('=')) {
23
+ bytes -= 1;
24
+ }
25
+ const output = new Uint8Array(bytes);
26
+ let inputIndex = 0;
27
+ for (let outputIndex = 0; outputIndex < bytes; outputIndex += 3) {
28
+ //Get the 4 numbers 0-63 encoded in the next 4 characters
29
+ const enc1 = charCodeToEncodedInt[base64String.charCodeAt(inputIndex++)];
30
+ const enc2 = charCodeToEncodedInt[base64String.charCodeAt(inputIndex++)];
31
+ const enc3 = charCodeToEncodedInt[base64String.charCodeAt(inputIndex++)];
32
+ const enc4 = charCodeToEncodedInt[base64String.charCodeAt(inputIndex++)];
33
+ //Read 8 total bits into each of the output values. Skip one byte
34
+ //for each padding = at the end of the string
35
+ output[outputIndex] = (enc1 << 2) | (enc2 >> 4);
36
+ if (enc3 != 64) {
37
+ output[outputIndex + 1] = ((enc2 & 15) << 4) | (enc3 >> 2);
38
+ }
39
+ if (enc4 != 64) {
40
+ output[outputIndex + 2] = ((enc3 & 3) << 6) | enc4;
41
+ }
42
+ }
43
+ return output;
44
+ }
45
+ exports.decodeBase64 = decodeBase64;
@@ -35,7 +35,9 @@ class DataSourceProxy extends propertystoreproxy_1.PropertyStoreProxy {
35
35
  return new collectionproxy_1.CollectionProxy(this.client.sendCommand("cc" /* CreateCollection */, {
36
36
  's': this.id,
37
37
  'n': name,
38
- 'f': schema.fields.map((field) => ({ 'n': field.name, 't': (0, fieldtypedefinition_1.serializeFieldTypeDefinition)(field.type) })),
38
+ 'f': schema.fields.map((field) => {
39
+ return { 'n': field.name, 't': (0, fieldtypedefinition_1.serializeFieldTypeDefinition)(field.type) };
40
+ }),
39
41
  'p': schema.primaryKey,
40
42
  }), this.client);
41
43
  }
@@ -5,10 +5,12 @@ const fieldtypedefinition_1 = require("../core/data/fieldtypedefinition/fieldtyp
5
5
  /** @ignore */
6
6
  function serializeSchemaDefinition(def) {
7
7
  return {
8
- 'Fields': def.fields.map((field) => ({
9
- 'Name': field.name,
10
- 'Type': (0, fieldtypedefinition_1.serializeFieldTypeDefinition)(field.type),
11
- })),
8
+ 'Fields': def.fields.map((field) => {
9
+ return {
10
+ 'Name': field.name,
11
+ 'Type': (0, fieldtypedefinition_1.serializeFieldTypeDefinition)(field.type),
12
+ };
13
+ }),
12
14
  'PrimaryKey': def.primaryKey,
13
15
  };
14
16
  }
@@ -16,10 +18,12 @@ exports.serializeSchemaDefinition = serializeSchemaDefinition;
16
18
  /** @ignore */
17
19
  function parseSchemaDefinition(def) {
18
20
  return {
19
- fields: def['Fields'].map((field) => ({
20
- name: field['Name'],
21
- type: (0, fieldtypedefinition_1.deserializeFieldTypeDefinition)(field['Type']),
22
- })),
21
+ fields: def['Fields'].map((field) => {
22
+ return {
23
+ name: field['Name'],
24
+ type: (0, fieldtypedefinition_1.deserializeFieldTypeDefinition)(field['Type']),
25
+ };
26
+ }),
23
27
  primaryKey: def['PrimaryKey'],
24
28
  };
25
29
  }
@@ -6,6 +6,7 @@ import { ElementProxy } from './document/elementproxy';
6
6
  import { GroupProxy } from './document/groupproxy';
7
7
  import { LineProxy } from './document/lineproxy';
8
8
  import { PageProxy } from './document/pageproxy';
9
+ import { FileUploadData } from './ui/menu';
9
10
  export interface XHRRequest {
10
11
  /** URL to request */
11
12
  url: string;
@@ -77,6 +78,13 @@ export declare class EditorClient {
77
78
  * @param callback function to execute when this action is invoked
78
79
  */
79
80
  registerAction(name: string, callback: (value: any) => JsonSerializable | void | Promise<any>): void;
81
+ /**
82
+ * Register a named action that receives file upload data. These callbacks can be used in
83
+ * Menu.addMenuItem as the file action.
84
+ * @param name The name of the action
85
+ * @param callback Function to execute when this action is invoked
86
+ */
87
+ registerFileUploadAction(name: string, callback: (files: FileUploadData[]) => void): void;
80
88
  /**
81
89
  * Remove the callback for a given action. If the action is later invoked, nothing will happen.
82
90
  * @param name name of the action to unregister
@@ -179,5 +187,22 @@ export declare class EditorClient {
179
187
  * @hidden
180
188
  */
181
189
  protected listenToEditor(): void;
190
+ /**
191
+ * Display an alert modal to the user
192
+ * @param text Body text to display in the alert modal
193
+ * @param title Title of the alert modal; defaults to the extension title specified in manifest.json
194
+ * @param buttonText Text for the OK button; defaults to "OK" (or a translation)
195
+ * @returns a Promise that resolves true if the user clicks OK, false if they otherwise dismiss the modal
196
+ */
197
+ alert(text: string, title?: string, buttonText?: string): import("./commandtypes").AlertResult;
198
+ /**
199
+ * Display a confirm modal to the user
200
+ * @param text Body text to display in the alert modal
201
+ * @param title Title of the alert modal; defaults to the extension title specified in manifest.json
202
+ * @param okText Text for the OK button; defaults to "OK" (or a translation)
203
+ * @param cancelText Text for the Cancel button; defaults to "Cancel" (or a translation)
204
+ * @returns a Promise that resolves true if the user clicks OK, false if they click Cancel or otherwise dismiss the modal
205
+ */
206
+ confirm(text: string, title?: string, okText?: string, cancelText?: string): import("./commandtypes").ConfirmResult;
182
207
  constructor();
183
208
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EditorClient = void 0;
4
4
  const commandtypes_1 = require("./commandtypes");
5
+ const base64_1 = require("./core/base64");
5
6
  const blockproxyregistry_1 = require("./document/blockclasses/blockproxyregistry");
6
7
  const blockproxy_1 = require("./document/blockproxy");
7
8
  const documentproxy_1 = require("./document/documentproxy");
@@ -131,6 +132,24 @@ class EditorClient {
131
132
  }
132
133
  this.callbacks.set(name, callback);
133
134
  }
135
+ /**
136
+ * Register a named action that receives file upload data. These callbacks can be used in
137
+ * Menu.addMenuItem as the file action.
138
+ * @param name The name of the action
139
+ * @param callback Function to execute when this action is invoked
140
+ */
141
+ registerFileUploadAction(name, callback) {
142
+ this.registerAction(name, (msg) => {
143
+ callback(msg['f'].map((rawFile) => {
144
+ if (rawFile['b']) {
145
+ return { fileName: rawFile['n'], text: rawFile['t'], binary: (0, base64_1.decodeBase64)(rawFile['b']) };
146
+ }
147
+ else {
148
+ return { fileName: rawFile['n'], text: rawFile['t'] };
149
+ }
150
+ }));
151
+ });
152
+ }
134
153
  /**
135
154
  * Remove the callback for a given action. If the action is later invoked, nothing will happen.
136
155
  * @param name name of the action to unregister
@@ -310,5 +329,26 @@ class EditorClient {
310
329
  return (_a = this.callbacks.get(msg['id'])) === null || _a === void 0 ? void 0 : _a(msg);
311
330
  });
312
331
  }
332
+ /**
333
+ * Display an alert modal to the user
334
+ * @param text Body text to display in the alert modal
335
+ * @param title Title of the alert modal; defaults to the extension title specified in manifest.json
336
+ * @param buttonText Text for the OK button; defaults to "OK" (or a translation)
337
+ * @returns a Promise that resolves true if the user clicks OK, false if they otherwise dismiss the modal
338
+ */
339
+ alert(text, title, buttonText) {
340
+ return this.sendCommand("a" /* Alert */, { 't': title, 'b': text, 'bt': buttonText });
341
+ }
342
+ /**
343
+ * Display a confirm modal to the user
344
+ * @param text Body text to display in the alert modal
345
+ * @param title Title of the alert modal; defaults to the extension title specified in manifest.json
346
+ * @param okText Text for the OK button; defaults to "OK" (or a translation)
347
+ * @param cancelText Text for the Cancel button; defaults to "Cancel" (or a translation)
348
+ * @returns a Promise that resolves true if the user clicks OK, false if they click Cancel or otherwise dismiss the modal
349
+ */
350
+ confirm(text, title, okText, cancelText) {
351
+ return this.sendCommand("c" /* Confirm */, { 't': title, 'b': text, 'o': okText, 'c': cancelText });
352
+ }
313
353
  }
314
354
  exports.EditorClient = EditorClient;
package/sdk/ui/menu.d.ts CHANGED
@@ -20,7 +20,7 @@ export interface CustomMenuItem {
20
20
  /** The text to display on the menu item */
21
21
  label: string;
22
22
  /** The registered action to run when the menu item is clicked */
23
- action: string;
23
+ action?: string;
24
24
  /** If specified, what action's return value should determine if this menu item is visible? */
25
25
  visibleAction?: string;
26
26
  /** If specified, what action's return value should determine if this menu item is disabled? */
@@ -29,13 +29,30 @@ export interface CustomMenuItem {
29
29
  menuType: MenuType;
30
30
  /** Where in that menu to display this item */
31
31
  location?: MenuLocation;
32
+ /** If specified, this menu item should launch a file picker */
33
+ file?: {
34
+ /** An action registered with EditorClient.registerFileUploadAction */
35
+ action: string;
36
+ /** An [accept string](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) as specified for HTML file inputs */
37
+ accept: string;
38
+ /** If true, only allow a single file to be selected for upload */
39
+ singleFileOnly?: boolean;
40
+ /** If true, send the file contents to the callback action as a Uint8Array as well as a plain text string */
41
+ binary?: boolean;
42
+ };
43
+ }
44
+ export interface FileUploadData {
45
+ fileName: string;
46
+ text: string;
47
+ binary?: Uint8Array;
32
48
  }
33
49
  export declare class Menu {
34
50
  private readonly client;
35
51
  constructor(client: EditorClient);
36
52
  /**
37
53
  * Add a new menu item to execute custom code. The action must be registered with
38
- * EditorClient.registerAction prior to using it in the menu.
54
+ * [EditorClient.registerAction](/extension-sdk/#classes_editorclient-EditorClient_registeraction)
55
+ * prior to using it in the menu.
39
56
  *
40
57
  * @param item The definition of the new menu item
41
58
  */
package/sdk/ui/menu.js CHANGED
@@ -26,12 +26,13 @@ class Menu {
26
26
  }
27
27
  /**
28
28
  * Add a new menu item to execute custom code. The action must be registered with
29
- * EditorClient.registerAction prior to using it in the menu.
29
+ * [EditorClient.registerAction](/extension-sdk/#classes_editorclient-EditorClient_registeraction)
30
+ * prior to using it in the menu.
30
31
  *
31
32
  * @param item The definition of the new menu item
32
33
  */
33
34
  addMenuItem(item) {
34
- if (!this.client.actionExists(item.action)) {
35
+ if (item.action && !this.client.actionExists(item.action)) {
35
36
  throw new Error('Unregistered action: ' + item.action);
36
37
  }
37
38
  if (item.visibleAction && !this.client.actionExists(item.visibleAction)) {
@@ -47,6 +48,14 @@ class Menu {
47
48
  'd': item.disabledAction,
48
49
  't': item.menuType,
49
50
  'loc': item.location,
51
+ 'f': item.file
52
+ ? {
53
+ 'a': item.file.action,
54
+ 'ac': item.file.accept,
55
+ 's': item.file.singleFileOnly,
56
+ 'b': item.file.binary,
57
+ }
58
+ : undefined,
50
59
  });
51
60
  }
52
61
  }