This is the set of initialization options the app can specify when initializing the app instance.
initializationCallback: function(Element, quip.apps.InitializationParameters) [required]
Function that will be called to initialize the app's view once the app instance has received all of its stored data.
menuCommands: Array<quip.apps.MenuCommand> [optional]
List of quip.apps.MenuCommands that can be used within the toolbar menus and context menus.
toolbarCommandIds: Array<string> [optional]
List of command IDs that should be used as the top-level toolbar buttons. quip.apps.DocumentMenuCommands.MENU_MAIN will automatically be used as the first button in the toolbar.
class Root extends React.Component {
render() {
return <div>Hello World!</div>
}
}
quip.apps.initialize({
initializationCallback: (root, params) => {
const rootRecord = quip.apps.getRootRecord();
if (params.isCreation) {
console.log("App instance created for the first time!")
}
ReactDOM.render(<Root />, root);
},
toolbarCommandIds: [
"addItem",
"deleteItem"
],
menuCommands: [
{
id: "addItem",
label: "Add Item",
handler: () => console.log("Add Item"),
},
{
id: "deleteItem",
label: "Delete Item",
handler: () => console.log("Delete Item"),
},
],
});
This set of parameters is passed to the initializationCallback function that the app registers and allows the app to initialize the data for the app appropriately.
isCreation: boolean
Whether this is the first time this app instance is being rendered after creation.
creationUrl: (string|undefined)
If this app instance was created from a URL paste in the document, this is the pasted URL.
payload: (string|undefined)
Contains the payload string either set by Setting Payload or by the data-live-app-payload
HTML attribute during API initialization.
This is the API call that initializes the environment for this app instance. This call allows the app to register menu commands for the toolbar and register a callback function that will be called once the data for the app instance has been sent over.
@param {quip.apps.InitOptions} initOptions
This represents a menu command that can be used as a button in the menu toolbar, as a menu option in a submenu within the toolbar, or as an option within a context menu.
id: string [required]
Must be unique. Can be one of quip.apps.DocumentMenuCommands.
label: string [optional]
String label displayed for the command
sublabel: string [optional]
String sublabel that is displayed for the command. Sublabel is right-aligned and rendered in gray text.
handler: function(string, Object, ?) [optional]
Handler function that is called when this command is selected. Should not be populated for commands that have isHeader: true or have subCommands.
isHeader: boolean [optional, default=False]
Whether or not this should be displayed as a header within the menu
subCommands: Array<string> [optional]
List of menu options to be displayed as a nested menu or submenu when this command is selected.
actionId: string [optional]
String representing a special action that should be taken by the host upon selection of this command. Should be one of quip.apps.DocumentMenuActions
actionParams: Object [optional]
Dictionary of parameters specific to the actionId.
actionStarted: function() [optional]
Callback function that is called when this action is started. For the SHOW_FILE_PICKER action, this is useful for showing uploading state.
These constants are references to the default menus shown for the app as well as utilities like the menu separator, or built-in commands that are supported by the host.
MENU_MAIN: "DocumentMenu-main"
Default first toolbar button shown for the app
SEPARATOR: "DocumentCommand-separator"
Allows the developer to include a separator either within a submenu or to separate toolbar button groups
COPY_ANCHOR_LINK: "DocumentCommand-copyAnchorLink"
Default command supported by the host that will copy an anchor link to this app instance to the user's clipboard
DELETE_APP: "DocumentCommand-deleteApp"
Default command supported by the host that will delete this instance of the app
These constants are for special actions that can be hooked up to MenuCommands.
SHOW_FILE_PICKER: 1
menuCommands: Array<quip.apps.MenuCommand> [optional]
List of quip.apps.MenuCommand objects to register with the host. If any of the IDs have been previously registered, this configuration will override the previous configuration.
toolbarCommandIds: Array<string> [optional]
List of command IDs to show as the app toolbar buttons. If not specified, the app retains the existing list of toolbar buttons.
disabledCommandIds: Array<string> [optional]
List of command IDs that should be shown as disabled in toolbar menus. If not specified, the app retains the existing list of disabled command IDs.
highlightedCommandIds: Array<string> [optional]
List of command IDs that should be shown as highlighted in toolbar menus. If not specified, the app retains the existing list of highlighted command IDs.
class Root extends React.Component {
addCommands = () => {
quip.apps.updateToolbar({
toolbarCommandIds: [
"addRow",
"deleteRow",
],
menuCommands: [
{
id: "addRow",
label: "Add Row",
handler: () => console.log("Add row"),
},
{
id: "deleteRow",
label: "Delete Row",
handler: () => console.log("Delete row"),
},
],
});
}
disableCommand = () => {
quip.apps.updateToolbar({
disabledCommandIds: ["addRow"],
});
}
render() {
return (
<div>
<button onClick={this.addCommands}>Add commands</button>
<button onClick={this.disableCommand}>Disable command</button>
</div>
);
}
}
// App is initialized with no toolbar
quip.apps.initialize({
initializationCallback: (root, params) => {
ReactDOM.render(<Root />, root);
},
});
Sends a message to the host to update various attributes of the menu commands available in the toolbar. This can update the configuration of the commands, which commands appear as top-level buttons in the toolbar, and also designate which commands should be disabled or highlighted in the menus.
@param {!quip.apps.ToolbarState} toolbarState [required]
Sends a message to the host to show a context menu with the given set of commandIds.
@param {!Event} event [required]
used to determine the location of where to show the context menu
@param {!Array<string>} commandIds [required]
List of previously registered command IDs that should be shown as the options for this context menu.
@param {Array<string>=} highlightedCommandIds [optional]
List of commandIds that should be shown as highlighted in this context menu.
@param {Array<string>=} disabledCommandIds [optional]
List of commandIds that should be shown as disabled in this context menu.
@param {function (): ?=} onDismiss [optional]
Callback function for when the context menu is dismissed (either due to the user selecting a command or clicking away)
@param {Object=} contextArg [optional]
Object that can be passed through to the handler for the command that the user selects.
class MyComponent extends React.Component {
state = {
canAdd: true,
canDelete: false,
}
handleClick = (e) => {
const { canAdd, canDelete } = this.state;
const disabledCommands = [];
if (!canAdd) disabledCommands.push("addItem");
if (!canDelete) disabledCommands.push("deleteItem");
quip.apps.showContextMenu(
e,
[
"addItem", // id from menuCommand in Initialization
"deleteItem", // id from menuCommand in Initialization,
quip.apps.DocumentMenuCommands.SEPARATOR,
quip.apps.DocumentMenuCommands.DELETE_APP,
],
[], // No highlighted commands
disabledCommands, // Disabled commands based on state
() => console.log("Context menu has been closed"),
);
}
render() {
return <div onClick={this.handleClick}>Show Context Menu</div>;
}
}
quip.apps.initialize({
initializationCallback: (root, params) => {
ReactDOM.render(<MyComponent />, root);
},
menuCommands: [
{
id: "addItem",
label: "Add Item",
handler: () => console.log("Add item called"),
},
{
id: "deleteItem",
label: "Delete Item",
handler: () => console.log("Delete item called"),
},
],
});
Like quip.apps.showContextMenu, but allows the caller to specify a DOM node at which to anchor the context menu.
@param {!Node} button
@param {!Array<string>} commandIds
@param {Array<string>=} highlightedCommandIds
@param {Array<string>=} disabledCommandIds
@param {function (): ?=} onDismiss
@param {Object=} contextArg
class MyComponent extends React.Component {
handleClick = (e) => {
quip.apps.showContextMenuFromButton(
this.refs["button"], // ref to the button DOM element
[
"addItem", // id from menuCommand in Initialization
"deleteItem", // id from menuCommand in Initialization,
quip.apps.DocumentMenuCommands.SEPARATOR,
quip.apps.DocumentMenuCommands.DELETE_APP,
],
[], // No highlighted commands
[], // No disabled commands
() => console.log("Context menu has been closed"),
);
}
render() {
return <button ref="button" onClick={this.handleClick}>
Show Context Menu From Button
</button>;
}
}
quip.apps.initialize({
initializationCallback: (root, params) => {
ReactDOM.render(<MyComponent />, root);
},
menuCommands: [
{
id: "addItem",
label: "Add Item",
handler: () => console.log("Add item called"),
},
{
id: "deleteItem",
label: "Delete Item",
handler: () => console.log("Delete item called"),
},
],
});
Returns whether this app is being rendered in a native mobile app context.
@return {boolean}
Returns whether this app is being rendered in a native app context (desktop or mobile).
@return {boolean}
Returns whether this app is being rendered in a desktop web browser.
@return {boolean}
Returns whether this app is being rendered in the native iOS app.
@return {boolean}
Returns whether this app is being rendered in the native Android app.
@return {boolean}
Returns whether this app is being rendered in the native Windows app.
@return {boolean}
Returns whether this app is being rendered in the native Mac app.
@return {boolean}
Returns a best-effort estimate of whether the user's client currently has an Internet connection.
@return {boolean}
quip.apps.EventType.ONLINE_STATUS_CHANGED
Returns a string representation of the currently running apps API version.
@return {string}
Returns whether the currently running apps API version is greater than or equal to the given version string.
@param {!string} version [required]
@return {boolean}
Returns the quip.apps.User object representing the current viewing Quip user. Returns null if the viewer is not logged in to Quip.
@return {(quip.apps.User|null)}
const listMembers = () => {
const viewingUser = quip.apps.getViewingUser();
const allMembers = quip.apps.getDocumentMembers();
console.log(viewingUser.getName() + " is currently viewing the document");
allMembers.forEach(user => console.log(user.getName() + " is a member of this document"));
}
quip.apps.addEventListener(quip.apps.EventType.DOCUMENT_MEMBERS_LOADED, listMembers);
Returns whether or not the viewer is logged in to Quip.
@return {boolean}
Returns an array of quip.apps.User objects representing the Quip users that are members of this thread.
@return {!Array<!quip.apps.User>}
quip.apps.EventType.DOCUMENT_MEMBERS_LOADED
Returns the ID representing this app in the Quip namespace. Only call this if you need a globally unique ID to store outside of Quip. For all other uses, please use quip.apps.getRootRecordId instead.
@return {!string}
Returns whether or not this app is currently focused in the document.
@return {boolean}
quip.apps.EventType.FOCUS
quip.apps.EventType.BLUR
Returns the id of the thread containing this app.
@return {string}
Returns whether or not the document containing this app is editable.
@return {boolean}
quip.apps.EventType.DOCUMENT_EDITABLE_CHANGED
Returns whether or not the current user is a thread member. This is different than read-only detection, because in the case of a link-shared document, a user can view a document before becoming a member, which will cause them to start receiving notifications for that thread. Users automatically join the thread if they make an edit.
Applications are expected to prevent any automatic edits (e.g. refreshing data on load) until this returns true, otherwise the automated edit will cause the user to become a thread member without their consent or intention.
When using this property, you'll want to subscribe to any changes so you can keep your local representation up to date.
@return {boolean}
quip.apps.EventType.THREAD_MEMBERSHIP_CHANGED
// In your root record constructor:
this.isThreadMember_ = quip.apps.isThreadMember();
quip.apps.addEventListener(quip.apps.EventType.THREAD_MEMBERSHIP_CHANGED, () => {
this.isThreadMember_ = quip.apps.isThreadMember();
this.notifyListeners();
});
Returns the width of the app container, in pixels.
@return {number}
quip.apps.EventType.CONTAINER_SIZE_UPDATE
BLUR
Event when the user switches focus away from this app instance in the document.
FOCUS
Event when the user switches focus to this app instance in the document.
CONTAINER_SIZE_UPDATE
Triggers when the container size for this app instance changes.
USER_PREFERENCE_UPDATE
Triggers when the UserPreferences for the viewing user are updated.
SITE_PREFERENCE_UPDATE
Triggers when the SitePreferences for the viewing user's site are updated.
DOCUMENT_MEMBERS_LOADED
Triggers when the list of document members for the current document have been loaded and are available for querying.
DOCUMENT_EDITABLE_CHANGED
Event when the editable status of the document has changed.
ONLINE_STATUS_CHANGED
Event when the user's client's online status has changed.
THREAD_MEMBERSHIP_CHANGED
Event when the users' thread membership has changed.
class FocusComponent extends React.Component {
state = {
focused: false,
}
componentDidMount() {
quip.apps.addEventListener(quip.apps.EventType.FOCUS, this.onFocus);
quip.apps.addEventListener(quip.apps.EventType.BLUR, this.onBlur);
}
componentWillUnmount() {
quip.apps.removeEventListener(quip.apps.EventType.FOCUS, this.onFocus);
quip.apps.removeEventListener(quip.apps.EventType.BLUR, this.onBlur);
}
onFocus = () => this.setState({ focused: true })
onBlur = () => this.setState({ focused: false })
render() {
return <div>focused {this.state.focused}</div>;
}
}
Subscribes a listener function to the given event type.
@param {!quip.apps.EventType} type [required]
@param {!function (): ?} listener [required]
Callback function that will be called when an event of this type happens.
Unsubscribes a listener function from the given event type.
@param {!quip.apps.EventType} type [required]
@param {!function (): ?} listener [required]
Callback function that should no longer be called when an event of this type happens.
Opens the given URL in a new browser window.
@param {!string} url [required]
class MyComponent extends React.Component {
handleClick = (e) => {
quip.apps.openLink("https://www.quip.com");
}
render() {
return <button onClick={this.handleClick}>
Open Link
</button>;
}
}
quip.apps.initialize({
initializationCallback: (root, params) => {
ReactDOM.render(<MyComponent />, root);
},
});
Creates a new message to be displayed in the document activity log. The message will be written from the current viewing user, will have the given text string, and will be attributed to this app.
@param {!string} text [required]
class MyComponent extends React.Component {
handleClick = (e) => {
const message = "Sample Message";
quip.apps.sendMessage(message);
}
render() {
return <button onClick={this.handleClick}>
Send Message
</button>;
}
}
quip.apps.initialize({
initializationCallback: (root, params) => {
ReactDOM.render(<MyComponent />, root);
},
});
Register a node as being "detached" from the app container and thus possibly extending outside of it. The host will make a best effort to still display such nodes and allow user interaction with them when the app has focus (but may disallow it if it presents a click-jacking risk).
@param {!Node} node [required]
class MyComponent extends React.Component {
addNode = (e) => {
quip.apps.addDetachedNode(this.refs["button"]);
}
removeNode = (e) => {
quip.apps.removeDetachedNode(this.refs["button"]);
}
handleClick = (e) => {
console.log("Button clicked!");
}
render() {
// The style attributes will force the DOM element
// to be rendered outside the container of the live app.
// Hence it will not be user interactable unless it is
// added as a detached node.
const style = {
position: "absolute",
top: "100%",
left: 0,
};
return <div>
<button onClick={this.addNode}>
Add Detached Node
</button>
<button onClick={this.removeNode}>
Remove Detached Node
</button>
<button
ref="button"
style={style}
onClick={this.handleClick}>
Click Me!
</button>
</div>;
}
}
quip.apps.initialize({
initializationCallback: (root, params) => {
ReactDOM.render(<MyComponent />, root);
},
});
Removes a previously-registered detached node.
@param {!Node} node [required]
Displays a backdrop over the document canvas that dims the document and app in order to allow the developer to render some UI that appears like a dialog. Takes an optional onDismiss callback that will be called when the backdrop is dismissed, e.g. through a user click.
@param {function (): ?=} onDismiss [optional]
class MyDialogComponent extends React.Component {
static propTypes = {
children: PropTypes.element.isRequired,
onDismiss: PropTypes.func,
};
componentDidMount() {
quip.apps.showBackdrop(this.props.onDismiss);
quip.apps.addDetachedNode(this.refs["node"]);
}
componentWillUnmount() {
quip.apps.dismissBackdrop();
quip.apps.removeDetachedNode(this.refs["node"]);
}
render() {
const dimensions = quip.apps.getCurrentDimensions();
const style = {
position: "absolute",
width: dimensions.width,
height: dimensions.height,
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: quip.apps.ui.ColorMap.BLUE.VALUE_LIGHT,
color: quip.apps.ui.ColorMap.BLUE.VALUE,
zIndex: 301,
};
return <div ref="node" style={style}>
{this.props.children}
</div>;
}
}
class MyComponent extends React.Component {
state = {
showDialog: false
}
handleSubmit = (e) => {
const value = this.refs["text"].value;
if (value.length > 0) {
this.setState({showDialog: true});
}
}
onDialogDismiss = () => {
this.setState({showDialog: false});
}
render() {
let dialog;
if (this.state.showDialog) {
const dialogChildren = <span>Dialog!</span>;
dialog = <MyDialogComponent
onDismiss={this.onDialogDismiss}
children={dialogChildren}/>;
}
return <div>
<input ref="text" type="text"></input>
<button onClick={this.handleSubmit}>Submit</button>
{dialog}
</div>;
}
}
quip.apps.initialize({
initializationCallback: (root, params) => {
ReactDOM.render(<MyComponent />, root);
},
});
Manually requests that the backdrop be dismissed. Allows the developer to pass skipCallback, which would avoid calling the onDismiss callback registered when showing the backdrop.
@param {boolean=} skipCallback [optional, default=False]
Returns the current width and height of this app instance.
@return {{width: number, height: number}}
Forces an immediate update of the app's dimensions. Updates will automatically happen in response to the app's DOM mutations, so this should only be used in special cases where the update needs to happen immediately.
Only available to apps with "scale" sizing. Tells the container the desired width and aspect ratio of the app. The width is saved, but does not affect the view since users are able to resize scale-sizing apps with columns. Passing along an updated aspect ratio (height / width) is critical if your app wants to change its shape. Otherwise, the container could improperly cutoff content or take up too much space in the document.
@param {number} width [required]
@param {number=} aspectRatio [optional]
Shows a drag handle for resizing when the app is focused. Resizing can only be enabled for apps with "scale" sizing.
class MyComponent extends React.Component {
componentDidMount() {
quip.apps.enableResizing();
}
render() {
return <div>Resize Me!</div>;
}
}
quip.apps.initialize({
initializationCallback: (root, params) => {
ReactDOM.render(<MyComponent />, root);
},
});
Hides the drag handle shown from quip.apps.enableResizing(). Has no effect if quip.apps.enableResizing() has not been called.
Registers the given iframe for the purposes of automatically handling focus events on the app.
@param {!Node} iframe [required]
Clears any iframes previously registered with quip.apps.registerEmbeddedIframe
Stores a payload string for this Live App. If a payload string already exists, calling this method will overwrite the payload. This string will be passed as a property in quip.apps.InitializationParameters to the initializationCallback function when the Live App is initialized. This string is also set as the value of the data-live-app-payload attribute when exporting this Live App as HTML.
@param {string} payload
A quip.apps.Record object is the basic unit of data storage in our API and represents a discrete unit of data within an app instance. By default, an app instance is created with a single quip.apps.RootRecord. The app may create additional quip.apps.Record instances to store discrete pieces of data that can be synced independently and can represent specific components within the app, such as a rich text box, an image, etc.
quip.apps.Record objects can store custom data for the app in the form of properties. Property data types can be any one of the following: a Javascript primitive (string, number, boolean, etc), a Javascript object or array that contains only primitives, a Record, or a list of Record objects, in the form of a quip.apps.RecordList. Record objects can be thought of as being analogous to JSON objects, with the primary difference being that defining a Record allows the developer to a) get independent syncing/merge behavior, b) add validation for their property data types and c) make use of our special data types, like quip.apps.RichTextRecord and quip.apps.ImageRecord.
Every Record or RecordList object is “owned” by another Record, in a hierarchical structure starting from the root. The developer does not need to explicitly specify these parent-child relationships; they are implicit upon creation of a Record or RecordList as a property of another Record.
All subclasses of quip.apps.Record defined by the developer must be registered with the apps API. The registration API is described here, along with the static methods and fields on the class that the developer can include in order to define the behavior of their quip.apps.Record class.
quip.apps.registerClassRegisters a subclass of quip.apps.Record (or RootRecord|ImageRecord|RichTextRecord) for use in the apps API. recordKey is required for all calls except for those registering subclasses of quip.apps.RootRecord. recordKey should be a string that is unique within the app namespace. It is stored in the Record data so that we can consistently instantiate the correct class for a stored Record object.
All registerClass() calls must be made before the quip.apps.initialize() call.
@param {!Function} recordClass
@param {string=} recordKey
class MyRecord extends quip.apps.RootRecord {
static getProperties = () => ({
date: "number",
})
}
quip.apps.registerClass(Root, "my-record");
class Root extends quip.apps.RootRecord {
static getProperties = () => ({
date: "number",
myRecord: MyRecord,
})
}
quip.apps.registerClass(Root, "root");
Developers can configure their quip.apps.Record subclasses by defining these static methods/fields on their class. One in particular, getProperties(), is required, as it describes the property schema for their Record and is used both to validate the property values that are being stored for the Record object as well as to construct the right types of Record and RecordList objects when setting property values.
The API is relatively strict about setting new property values on Record objects; if the property name has not been defined by getProperties() or if the value does not match the data type defined in getProperties(), the set() call will fail. In addition to the properties that the developer defines in getProperties(), subclasses inheriting from Quip's special Record types (e.g. quip.apps.RichTextRecord or quip.apps.ImageRecord) can also inherit type-specific properties. These properties will be listed for each of the subclasses below, but the property names will be prefixed by the Record type (e.g. RichText_defaultText). Developers will also not be allowed to define property names with these prefixes to avoid collisions.
Special Record types:class MyRecord extends quip.apps.Record {
static getProperties = () => ({
title: "string",
count: "number",
})
}
quip.apps.registerClass(MyRecord, "my-record");
class RootRecord extends quip.apps.RootRecord {
static getProperties = () => ({
title: "string",
items: "array",
editable: "boolean",
text: quip.apps.RichTextRecord,
subRecord: MyRecord,
})
static getDefaultProperties = () => ({
items: [],
editable: true,
text: { RichText_placeholderText: "Type In Here!" },
})
}
quip.apps.registerClass(RootRecord, "root-record");
class Root extends React.Component {
setTitle = () => {
const { record } = this.props;
record.set("title", "new title");
}
addItem = () => {
const { record } = this.props;
const current = record.get("items");
record.set("items", current.concat(`new item ${current.length + 1}`));
}
toggleEditable = () => {
const { record } = this.props;
record.set("editable", !record.get("editable"));
}
addRecordData = () => {
const { record } = this.props;
record.set("subRecord", { title: "title", count: 5 });
}
render() {
const { record } = this.props;
return (
<div>
<button onClick={this.setTitle}>Set title</button>
<button onClick={this.addItem}>Add item</button>
<button onClick={this.toggleEditable()}>Toggle editable</button>
<button onClick={this.addRecordData}>Add record data</button>
<br />
{record.get("title") || "No title set"}
<br />
{record.get("items").join(", ") || "No items set"}
<br />
{record.get("editable") ? "editable" : "not editable"}
<br />
{record.get("subRecord")
? JSON.stringify(record.get("subRecord").getData())
: "no subrecord data"
}
<br />
<quip.apps.ui.RichTextBox record={record.get("text")} />
</div>
);
}
}
quip.apps.initialize({
initializationCallback: (root, params) => {
ReactDOM.render(<Root record={quip.apps.getRootRecord()} />, root);
},
});
Required static method on all classes registered using quip.apps.registerClass(). This should return a "schema" of the properties that will be stored for this Record type, in the following format:
{
$PROPERTY_NAME1: "string"|"number"|"boolean"|"object"|"array"|Function|quip.apps.RecordList.Type,
$PROPERTY_NAME2: ...,
...
}
The value for each property should indicate the data type of the property being stored, and can either be one of a list of hard-coded primitive types, a Record constructor function (e.g. quip.apps.ImageRecord, quip.apps.RichTextRecord, or a Record subclass that the developer has defined), or a list type generated using quip.apps.RecordList.Type().
Example:
static getProperties() {
return {
"header": CardRecord,
"cards": quip.apps.RecordList.Type(CardRecord),
"color": "string"
};
}
@return {!Object<string, (string|Function)>}
Optional static method that returns default values for some or all of the properties defined on this Record.
@return {Object<string, *>}
Returns the stored property value for this key. If a value has not yet been set but this property has a value in getDefaultProperties(), this will set the default value from getDefaultProperties() and return it.
@param {string} key
@return {*}
Returns true if this Record has a value stored for this property.
@param {string} key
@return {boolean}
Returns a dictionary of all the property values stored in the Record.
@return {!Object}
This sets the value for a property on the given Record. We use the property schema to determine whether we should create a Record or RecordList, and if we do, what type of Record to create.
When setting a property that's of type Record or RecordList, if the property already has a stored value, this will throw an error. Developers need to clear the property before resetting it to a new value.
This returns the Record whose property is being set to enable chaining of setters.
@param {!string} propertyName [required]
@param {*} value [required]
@return {quip.apps.Record}
Clears a property on the Record. If the property is a Record or a RecordList, then the Record/RecordList will be deleted, unless skipDelete is true. If skipDelete is true, then we will not call delete on the Record/RecordList and will return a reference to it. This is intended to be used by developers to move a Record property from one object to another.
@param {string} key [required]
@param {boolean=} skipDelete [optional]
@return {quip.apps.Record|quip.apps.RecordList|null}
Clears all properties for the given Record.
Gets a quip.apps.Record by ID. Returns null if no Record exists for that ID.
@param {!string} id [required]
@return {(quip.apps.Record|null)}
Gets a pointer to the RootRecord for this app instance.
@return {!quip.apps.RootRecord}
Gets the ID of the RootRecord for this app instance.
@return {!string}
Returns the ID for this Record. This ID is guaranteed to be unique within this app instance, but if you are planning to store this ID in a datastore external to Quip, please use quip.apps.Record.prototype.getUniqueId instead.
@return {string}
Returns an ID for this Record that is safe to store in an external datastore. For Record references that are stored with Quip, please use quip.apps.Record.prototype.getId instead.
@return {string}
Returns whether or not this Record instance has been deleted.
@return {boolean}
Sets this Record as deleted. If this Record is in a RecordList, it will be removed from the list. All Record and RecordList properties of this Record are also deleted.
Override this method to do initialization for this Record instance upon instantiation.
Subscribe to changes to this Record. The listener function will be called for both local and remote changes to this Record.
@param {function (!quip.apps.Record): ?} listener
Unsubscribe from changes to this Record.
@param {function (!quip.apps.Record): ?} listener
Optional static field that defines the data version that new Records of this type should be created with.
Returns a number representing the version for the data schema this Record was written with. Can be used to help the developer differentiate between Record data written with different versions of code. Reflects the data version specified on the class at Record creation or through calls to setDataVersion(). If a data version was unspecified, this returns 0.
@return {number}
Updates the data version associated with this Record instance.
@param {!number} version [required]
Returns the Record object that contains either this Record as a property or, if this Record lives within a RecordList, returns the Record containing this RecordList as a property.
Only returns null for the RootRecord.
@return {(quip.apps.Record|null)}
Returns the sibling Record that precedes this one if this Record is in a RecordList. Returns null if this Record is not in a RecordList or is the first Record in the list.
@return {(quip.apps.Record|null)}
Returns the sibling Record that follows this one if this Record is in a RecordList. Returns null if this Record is not in a RecordList or is the last Record in the list.
@return {(quip.apps.Record|null)}
If this Record is contained in a RecordList, this returns the RecordList.
@return {(quip.apps.RecordList|null)}
Override this to return whether this Record supports commenting. By default, this returns false.
Note that this has no effect when overridden in the RootRecord. In order to control whether commenting is available on the app as a whole, please see the disable_app_level_comments field in the app manifest.
@return {boolean}
class RootRecord extends quip.apps.RootRecord {
static getProperties = () => ({
commentableRecord: CommentableRecord,
})
static getDefaultProperties = () => ({
commentableRecord: {},
})
}
quip.apps.registerClass(RootRecord, "root-record");
class CommentableRecord extends quip.apps.Record {
static getProperties = () => ({})
getDom() {
return this.node;
}
setDom(node) {
this.node = node;
}
supportsComments() {
return true;
}
}
quip.apps.registerClass(CommentableRecord, "commentable-record");
class Root extends React.Component {
render() {
const commentableRecord = quip.apps.getRootRecord().get("commentableRecord");
return (
<div ref={(c) => commentableRecord.setDom(c)}>
Comment
<quip.apps.ui.CommentsTrigger
record={commentableRecord}
showEmpty={true}
/>
</div>
);
}
}
quip.apps.initialize({
initializationCallback: (root, params) => {
ReactDOM.render(<Root />, root);
},
});
Override this method to return the DOM node that represents this Record. This is required if this Record supports comments, as this DOM node will be used to highlight the Record when showing the corresponding comments.
@return {!Node}
Sends a message to the host to show the comments UI for the given Record ID.
@param {!string} id [required]
Subscribe to changes in the comments associated with this Record ID.
@param {function (!quip.apps.Record): ?} listener
Unsubscribe from changes to the comments associated with this Record.
@param {function (!quip.apps.Record): ?} listener
Used to declare a property type that is a RecordList containing a particular type of Record.
@param {!Function} recordConstructor [required]
Class name for the Record type that this RecordList contains
@return {!quip.apps.Record.Type}
class MyRecord extends quip.apps.Record {
static getProperties = () => ({
title: "string",
disabled: "boolean",
})
static getDefaultProperties = () => ({
disabled: true,
})
}
quip.apps.registerClass(MyRecord, "my-record");
class RootRecord extends quip.apps.RootRecord {
static getProperties = () => ({
myRecords: quip.apps.RecordList.Type(MyRecord),
})
static getDefaultProperties = () => ({
myRecords: [],
})
}
quip.apps.registerClass(RootRecord, "root-record");
class MyComponent extends React.Component {
render() {
const record = quip.apps.getRootRecord();
return (
<div>
{record.get("myRecords").getRecords().map((r) => (
<div key={r.getId()}>
{r.get("title")}
{" - "}
{r.get("disabled") ? "disabled" : "active"}
</div>
))}
</div>
);
}
}
quip.apps.initialize({
initializationCallback: (root, params) => {
const rootRecord = quip.apps.getRootRecord();
const myRecords = rootRecord.get("myRecords");
if (params.isCreation) {
myRecords.add({ title: "Title 2" });
myRecords.add({ title: "Title 3" });
myRecords.add({ title: "Title 1" }, 0);
myRecords.get(0).set("disabled", false);
}
ReactDOM.render(<MyComponent />, root);
},
});
Returns the ID of this RecordList.
@return {string}
Returns the Record at the given index in this list.
@param {!number} index [required]
@return {(quip.apps.Record|null)}
Returns an array of all the Record objects in this list.
@return {!Array<quip.apps.Record>}
Returns whether or not the given recordId is contained in this list.
@param {!string} recordId [required]
@return {boolean}
Returns the number of Record objects in this list.
@return {number}
Returns the index of the given recordId in this list. Returns -1 if this recordId is not contained in this list.
@param {!string} recordId [required]
@return {number}
@return {boolean}
This creates a new Record and adds it to this RecordList. We use the list type definition to determine what type of Record to create from the fields in the value. If index is not specified, the Record is appended to the end of the list.
Returns the newly created Record.
@param {!Object} value [required]
@param {number=} index [optional]
@return {!quip.apps.Record}
Moves the given record to a new index in the list. If record is not currently in this list, it will add this Record to the list (and remove it from the list it currently lives in, if there is one). If record is currently the property of another Record object, this returns false and does not execute the move.
@param {!quip.apps.Record} record [required]
@param {!number} index [required]
@return {boolean}
Removes the given Record from this list. Deletes the removed record unless skipDelete is true.
@param {!quip.apps.Record} record [required]
@param {boolean=} skipDelete [optional]
@return {boolean}
Deletes this list. Also deletes all Record objects in this list.
Subscribes the listener function to changes in this RecordList. This listener function is only fired when the list of Records changes and not for changes in each Record; the developer is responsible for subscribing to changes in individual Records if needed.
@param {function ((quip.apps.RecordList|null)): ?} listener
@return {undefined}
Unsubscribes this listener function from changes in this RecordList.
@param {function ((quip.apps.RecordList|null)): ?} listener
@return {undefined}
A RichTextRecord is a special type of Record that corresponds to a rich text box within the app. The developer can create and delete RichTextRecord objects and can render them within their app by using the React component: <quip.apps.ui.RichTextBox/>.
{
// Optional. Allows the creator to specify the default text that should appear
// in the RichTextBox on creation.
RichText_defaultText: "string",
// Optional. Allows the creator to specify placeholder text that should
// appear in the RichTextBox whenever it is empty.
RichText_placeholderText: "string",
}
class DraggableCard extends quip.apps.RichTextRecord {
static getProperties = () => ({
color: "string",
})
}
quip.apps.registerClass(DraggableCard, "draggable-card");
class RootRecord extends quip.apps.RootRecord {
static getProperties = () => ({
title: quip.apps.RichTextRecord,
cards: quip.apps.RecordList.Type(DraggableCard),
})
static getDefaultProperties = () => ({
title: { RichText_placeholderText: "Add a title" },
cards: [],
})
}
quip.apps.registerClass(RootRecord, "root");
class Root extends React.Component {
render() {
const record = quip.apps.getRootRecord();
const title = record.get("title");
const cards = record.get("cards").getRecords();
return (
<div>
<quip.apps.ui.RichTextBox record={title} />
{cards.map((card) => (
<quip.apps.ui.RichTextBox
key={card.getId()}
record={card}
color={card.get("color")}
/>
))}
</div>
);
}
}
quip.apps.initialize({
initializationCallback: (root, params) => {
const rootRecord = quip.apps.getRootRecord();
const cardList = rootRecord.get("cards");
if (params.isCreation) {
cardList.add({
color: "BLUE",
RichText_defaultText: "Blue card!"
});
cardList.add({
color: "RED",
RichText_defaultText: "Red card!"
});
}
ReactDOM.render(<Root />, root);
},
});
Exports the current contents of the RichTextRecord to a Markdown string.
@return {string}
Returns true if there is no text in this RichTextRecord.
@return {boolean}
Subscribes this listener function to any changes in the contents of this RichTextRecord.
@param {function(!quip.apps.RichTextRecord): ?} listener [required]
Unsubscribes this listener function from changes in the content of this RichTextRecord.
@param {function(!quip.apps.RichTextRecord): ?} listener [required]
Sets focus to this RichTextRecord. fromPrevious allows you to configure whether the focus should be set at the beginning or the end of this RichTextRecord's content.
@param {boolean=} fromPrevious [optional, default=True]
Sets the browser selection to select all content within this RichTextRecord.
Replaces the content of this RichTextRecord with the given text string. Requires that the RichTextRecord is actively mounted in a RichTextBox.
Constants representing different rich text styles available in the RichTextBox. Can be used in the allowedStyles prop in quip.apps.ui.RichTextBox to specify which styles should be allowed in the RichTextBox.
@enum {number}
TEXT_PLAIN:1
TEXT_H1:2
TEXT_H2:3
TEXT_H3:4
TEXT_CODE:5
TEXT_BLOCKQUOTE:6
TEXT_PULL_QUOTE:7
LIST_BULLET:8
LIST_NUMBERED:9
LIST_CHECKLIST:10
HORIZONTAL_RULE:11
IMAGE:12
An ImageRecord is a special type of Record that corresponds to an image rendered within the app. The developer can create and delete ImageRecord objects and can render them within their app by using the React component: <quip.apps.ui.Image/>.
{
// Required. Specifies the sizes (widths, in pixels) that Quip should save
// thumbnails for the uploaded image file.
Image_requestedThumbnailWidths: "array"
}
class CaptionedImageRecord extends quip.apps.Record {
static getProperties = () => ({
image: quip.apps.ImageRecord,
caption: quip.apps.RichTextRecord,
})
static getDefaultProperties = () => ({
image: {},
caption: { RichText_placeholderText: "Add a caption" },
})
}
quip.apps.registerClass(CaptionedImageRecord, "captioned-image");
class RootRecord extends quip.apps.RootRecord {
static getProperties = () => ({
captionedImage: CaptionedImageRecord,
})
static getDefaultProperties = () => ({
captionedImage: {},
})
}
quip.apps.registerClass(RootRecord, "root");
class ImageWithCaption extends React.Component {
render() {
const record = quip.apps.getRootRecord();
const captionedImage = record.get("captionedImage");
return (
<div>
<quip.apps.ui.Image
record={captionedImage.get("image")}
width={200}
/>
<quip.apps.ui.RichTextBox
record={captionedImage.get("caption")}
allowImages={false}
/>
</div>
);
}
}
quip.apps.initialize({
initializationCallback: (root, params) => {
ReactDOM.render(<ImageWithCaption />, root);
},
});
Prompts the user with a file picker to have them choose an image file. Once the file is successfully uploaded to Quip and is accessible, listeners for this Record will be notified.
Returns whether this ImageRecord has already been successfully associated with an image file.
@return {boolean}
Sends a message to the host to open this image in an external lightbox.
Sends a message to the host to trigger a download of the original image file to the user's machine.
Convenience callback to allow an ImageRecord to handle the actionStarted phase for a MenuCommand that uses quip.apps.DocumentMenuActions.SHOW_FILE_PICKER.
Convenience callback to allow an ImageRecord to handle the result for a MenuCommand that uses quip.apps.DocumentMenuActions.SHOW_FILE_PICKER.
@param {Array<Object>} blobsWithThumbnails
A CanvasRecord is a special type of Record that adds support for position-based comments. For example, the Image Live App uses this to allow comments to be added anywhere on the image. CanvasRecord can be treated as an opaque object for developers who pass it into a <quip.apps.ui.Canvas> React component.
Represents the location of a comment anchor within a Canvas.
quip.apps.CanvasRecord.CommentAnchorRecord.prototype.getXFractionReturns the x- and y-coordinates of the comment anchor. These are in fraction of the canvas size, so 0.25 for getXFraction means a quarter of the width away from the left-edge & 0.99 for getYFraction means near the bottom.
@return {number}
A quip.apps.User is a representation of a Quip user for the app's use. They are immutable but allow the app to access and display some basic information about the user. To render a profile picture for the user, you can use <quip.apps.ui.ProfilePicture/>.
Gets the quip.apps.User object for the given ID. Will only return data if the user is the viewing user or a document member. Returns null if no user with that ID was found.
@param {!string} userId [required]
@return {(quip.apps.User|null)}
Returns the unique ID representing this user.
@return {string}
Returns the user's name.
@return {string}
Returns the user's first name.
@return {string}
Returns the unique ID representing this user's company. If the user's company does not exist, returns an empty string.
@return {string}
A quip.apps.Blob instance allows the app to store some binary data with Quip that can be accessed either via URL or as an ArrayBuffer. quip.apps.Blobs can be created either by passing in an ArrayBuffer of binary data or by showing a file picker to the user and allowing them to upload a file from their machine.
Creates a new Blob instance from the given data. If there’s a filename associated with this data, it can be passed in.
@param {!ArrayBuffer} data [required]
ArrayBuffer of data to create the blob from
@param {string=} filename [optional]
Filename to attach to the blob
@return {!quip.apps.Blob)}
blob: quip.apps.Blob,
thumbnails: (Array<quip.apps.Blob>|undefined)
Sends a message to the host to show a file picker to the user. The callback will be triggered after the file that the user has selected has been successfully uploaded to Quip and can be accessed via URL.
@param {function (): ?} onFilesPicked [required]
Callback function that will be triggered after the user has selected a file in the picker.
@param {function ((Array<quip.apps.BlobWithThumbnails>|null)): ?} onFilesUploaded [required]
Callback function that will be triggered after the file the user has selected has been successfully uploaded to Quip and can be accessed via URL.
@param {Array<string>=} allowedTypes [optional]
List of MIME types that the user is allowed to choose from in the file picker
@param {Array<number>=} requestedThumbnailWidths [optional]
List of thumbnail widths for Quip to generate from the selected file. If specified, you must also specify a list of allowedTypes that only includes image MIME types.
Returns the ID for this Blob. If this Blob has not yet been stored to Quip, this will return null.
@return {(null|string)}
Returns whether or not the data for this Blob has been loaded.
@return {boolean}
Schedules a callback function that will be triggered when the data for this Blob has been loaded from Quip. If the data has already been loaded, loadedCallback will be triggered immediately. Also takes an optional loadErrorCallback that will be triggered if an error is encountered when trying to perform the load.
@param {function (!quip.apps.Blob): ?} loadedCallback [required]
@param {function (!quip.apps.Blob): ?=} loadErrorCallback [optional]
Returns the data for this Blob. If the data hasn't been loaded yet, this will return null.
@return {(ArrayBuffer|null)}
Returns the URL for this Blob. If the Blob has not been loaded yet, this will return null.
@return {(null|string)}
Returns the filename associated with this Blob. If the Blob has not been loaded yet, this will return null.
@return {(null|string)}
Triggers a download in the browser for the contents of this Blob, as a file.
Triggers the host document to open the given Blob in a lightbox. Only supported for Blobs containing image data.
We allow the app to store a small amount of preference data on the viewing User or the viewing user's Site. The preferences are stored as a series of string keys and values. This storage is intended to allow apps to store data that should be tied to the Quip user rather than to any particular instance of the app.
Returns the Preferences object that's scoped to the viewing User. Returns null if there is no viewing user.
@return {(quip.apps.Preferences|null)}
quip.apps.EventType.USER_PREFERENCE_UPDATE
Returns the Preferences object that's scoped to the viewing User's Site. Returns null if there is no viewing user or no site associated with the user.
@return {(quip.apps.Preferences|null)}
quip.apps.EventType.SITE_PREFERENCE_UPDATE
Returns a dictionary of the preferences that have been stored.
@return {!Object<string,string>}
Returns the string value for the given preference key.
@param {!string} key [required]
@return {string}
Saves the given set of preferences. Only keys that are included in this prefs dictionary are modified, any other keys that already exist on the user are preserved. In order to delete a preference key, the caller must include that key in the prefs object with an undefined value.
@param {!Object<string,string>} prefs [required]
Clears all stored preferences.
record: PropTypes.instanceOf(quip.apps.RichTextRecord).isRequired
RichTextRecord object containing the data for this RichTextBox.
scrollable: PropTypes.bool [default=false]
Controls whether the RichTextBox will allow vertical scrolling once the contents have grown larger than the maxHeight. By default, the extra content tighter is hidden
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]) [default=300]
Width (px) for the RichTextBox. Can also be a string like "100%".
minHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]) [default=20]
Minimum height (px) for the RichTextBox regardless of its contents Can also be a string like "100%".
maxHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]) [default=300]
Maximum height (px) for the RichTextBox. If its contents grow past this height, the RichTextBox may become scrollable depending on the scrollable prop Can also be a string like "100%".
disableInlineMenus: PropTypes.bool [default=false]
Controls whether the default inline format menu and link inspector are shown
disableAutocomplete: PropTypes.bool [default=false]
Controls whether autocomplete is enabled in the RichTextBox
onComponentHeightChanged: PropTypes.func
Callback for when the height of this RichTextBox has changed
useDocumentTheme: PropTypes.bool [default=false]
Whether the contents of the RichTextBox should be rendered using the host document theme. If this is false, contents are rendered in Lightning theme
allowedStyles: PropTypes.arrayOf(quip.apps.RichTextRecord.Style) [default=[]]
If specified, this provides an exact list of the formatting styles that will be permitted to be created in this RichTextBox and will supersede any additional allow* props specified.
allowImages: PropTypes.bool [default=true]
Whether this RichTextBox allows insertion of images
allowLists: PropTypes.bool [default=true]
Whether this RichTextBox allows insertion of lists
allowHeadings: PropTypes.bool [default=false]
Whether this RichTextBox allows heading font styles
allowSpecialTextStyles: PropTypes.bool [default=false]
Whether this RichTextBox allows text styles like Code Block, Blockquote or Pull Quote
allowHorizontalRules: PropTypes.bool [default=false]
Whether this RichTextBox allows insertion of horizontal rules
maxListIndentationLevel: PropTypes.number
Sets a max indentation level for lists contained in this RichTextBox
readOnly: PropTypes.bool [default=false]
Whether this RichTextBox should be rendered in read-only mode
onFocus: PropTypes.func
Callback for when this RichTextBox gains focus
onBlur: PropTypes.func
Callback for when this RichTextBox loses focus
handleKeyEvent: PropTypes.func
Callback for keyboard events when this RichTextBox is focused. If handleKeyEvent returns true, the event is not passed to the RichTextBox to be processed
color: PropTypes.oneOf(Object.keys(quip.apps.ui.ColorMap))
Controls the color of the text and the cursor
record: PropTypes.instanceOf(quip.apps.Record).isRequired,
showEmpty: PropTypes.bool [default=false]
Whether or not the CommentsTrigger should be rendered if there are no comments on the Record
record: PropTypes.instanceOf(quip.apps.ImageRecord).isRequired,
ImageRecord containing the image data to be displayed.
minWidth: PropTypes.number [default=100]
Specifies a minimum width (px) for this image. Prevents resizing to smaller than this width.
defaultWidth: PropTypes.number
Specifies the default width (px)
width: PropTypes.number
height: PropTypes.number
These props specify the size (px) of the rendered image. If not specified, the image is rendered according to the size stored in the ImageRecord
borderRadius: PropTypes.number [default=0]
Specify a value between 0 and 0.5 to control how rounded the image corners should be. A value of 0 gives square corners, a value of 0.5 makes the image circular (if the image has the same width and height)
placeholderWidth: PropTypes.number [default=100]
placeholderHeight: PropTypes.number [default=100]
When the ImageRecord does not have an associated image file yet, these props control the size of the placeholder
allowResizing: PropTypes.bool [default=true]
Whether to allow the user to resize the image
onSizeChanged: PropTypes.func,
Callback for when the rendered size of the image has changed
onResizingModeChanged: PropTypes.func,
Callback for when the image enters and exits resizing mode
onEditingModeChanged: PropTypes.func,
Callback for when the image enters and exits editing mode
responsiveToContainerWidth: PropTypes.bool [default=false]
When set to true, the sizing of the image will respect the app container width. If the size of the image is wider than the container width, it will be capped at the container width and will not allow resizing to be wider than the container
record: PropTypes.instanceOf(quip.apps.CanvasRecord).isRequired
CanvasRecord object that acts as the data store for the Canvas.
isInCommentMode: PropTypes.bool [default=false]
Whether the user is adding a CommentTrigger to the Canvas. This causes the Live App to show a comment bubble that follows the mouse. This behavior is only supported for Canvases that cover the entire Live App. Otherwise, use quip.apps.ui.Canvas.prototype.addCommentAtPoint.
onCommentAdd: PropTypes.func
Zero-argument callback when a CommentAnchor is added when isInCommentMode is true. You should pass false for isInCommentMode after this.
cancelCommentMode: PropTypes.func
Zero-argument callback when the user cancels out of adding a comment bubble from isInCommentMode true. You should pass false for isInCommentMode after this.
isCommentAnchorValidCallback: PropTypes.func
Callback used to flag comment anchors not in valid locations. This is used by <quip.apps.ui.Image/> to show which comment anchors will be removed if a crop is committed. Takes a quip.apps.ui.Canvas.CommentAnchorRecord object as an argument. Expects to have a boolean returned where false means to display the red, cross-out bubble. If not passed in, all comment anchors are assumed to be in valid locations.
Adds a CommentTrigger at the Event location.
@param {number} clientX
@param {number} clientY
These are the clientX & clientY taken from an Event
user: PropTypes.instanceOf(quip.apps.User).isRequired
quip.apps.User object to display the profile picture for
size: PropTypes.number.isRequired
Size in px that is used for the profile picture width and height
round: PropTypes.bool [default=false]
Whether to render the profile picture as round (defaults to square)
fallbackToInitials: PropTypes.bool [default=true]
If the user doesn't have a profile picture, we will render their initials as the profile picture if this is true, otherwise we will render the default placeholder profile picture
initialSelectedDateMs: PropTypes.number.isRequired
The initially selected date in the calendar, specified as milliseconds since 1/1/1970
onChangeSelectedDateMs: PropTypes.func.isRequired
Callback for when the user selects a new date. The function will be invoked with a single argument: the new selected date in milliseconds.
text: PropTypes.string.isRequired
Text for label displayed on the button
primary: PropTypes.bool [default=false]
Gives the button different styling (blue background, white text)
disabled: PropTypes.bool [default=false]
Gives the button disabled styling
type: PropTypes.string
Sets the type attribute of this button (e.g. "submit")
onClick: PropTypes.func
onClick handler for the button
The map below should help make it easy to build card components in your app. In the Record, you should store the KEY value for colors and then look up the values in this map at render time - that way, if the colors change, your UI will remain consistent with the rest of Quip. A typical card UI component is meant to have VALUE_STROKE for the border, VALUE_LIGHT for the background and VALUE for text and interior UI controls (chevrons, etc.). When selected, the background becomes the VALUE and the text/UI controls become WHITE.VALUE.
quip.apps.ui.ColorMap/**
* @enum {{
* KEY: string,
* VALUE: string,
* VALUE_LIGHT: string,
* VALUE_STROKE: string
* }}
*/
quip.apps.ui.ColorMap = {
RED: {
KEY: "RED",
LABEL: "Red",
VALUE: string,
VALUE_LIGHT: string,
VALUE_STROKE: string,
},
ORANGE: {
KEY: "ORANGE",
LABEL: "Orange",
VALUE: string,
VALUE_LIGHT: string,
VALUE_STROKE: string,
},
YELLOW: {
KEY: "YELLOW",
LABEL: "Yellow",
VALUE: string,
VALUE_LIGHT: string,
VALUE_STROKE: string,
},
GREEN: {
KEY: "GREEN",
LABEL: "Green",
VALUE: string,
VALUE_LIGHT: string,
VALUE_STROKE: string,
},
BLUE: {
KEY: "BLUE",
LABEL: "Blue",
VALUE: string,
VALUE_LIGHT: string,
VALUE_STROKE: string,
},
VIOLET: {
KEY: "VIOLET",
LABEL: "Violet",
VALUE: string,
VALUE_LIGHT: string,
VALUE_STROKE: string,
},
WHITE: {
KEY: "WHITE",
LABEL: "White",
VALUE: string,
VALUE_LIGHT: string,
VALUE_STROKE: string,
}
};
These classes specify color
, background-color
, fill
, stroke
, and border-color
respectively:
// Text color
quip-color-text
quip-bg-color-text
quip-fill-color-text
quip-stroke-color-text
quip-border-color-text
// Secondary color
quip-color-text-secondary
quip-bg-color-text-secondary
quip-fill-color-text-secondary
quip-stroke-color-text-secondary
quip-border-color-text-secondary
// Action color
quip-color-action
quip-bg-color-action
quip-fill-color-action
quip-stroke-color-action
quip-border-color-action
// Selection color
quip-color-selection
quip-bg-color-selection
quip-fill-color-selection
quip-stroke-color-selection
quip-border-color-selection
// Red
quip-color-red
quip-bg-color-red
quip-fill-color-red
quip-stroke-color-red
quip-border-color-red
// Orange
quip-color-orange
quip-bg-color-orange
quip-fill-color-orange
quip-stroke-color-orange
quip-border-color-orange
// Yellow
quip-color-yellow
quip-bg-color-yellow
quip-fill-color-yellow
quip-stroke-color-yellow
quip-border-color-yellow
// Green
quip-color-green
quip-bg-color-green
quip-fill-color-green
quip-stroke-color-green
quip-border-color-green
// Blue
quip-color-blue
quip-bg-color-blue
quip-fill-color-blue
quip-stroke-color-blue
quip-border-color-blue
// Violet
quip-color-violet
quip-bg-color-violet
quip-fill-color-violet
quip-stroke-color-violet
quip-border-color-violet
These classes specify font-size
and line-height
:
quip-text-plain
quip-text-h1
quip-text-h2
quip-text-h3
URL configurations are typically useful for authenticating with a company intranet or another service you control. It relies on using cookies, which are set during the login flow, with cross origin AJAX requests to authenticate the API calls. See Cross-Origin Resource Sharing (CORS): Request with Credentials for more details.
Name [required]
String name for the auth configuration. Must only contain alphanumeric characters, dashes, and underscores and be between 1 and 100 characters long. Must be unique for each auth config each app.
Login Url [required]
Url for the login page of your website. When logging in, the user will be redirected to this url, along with a redirect_uri
query parameter. Upon a successful login, the user should be redirected to redirect_uri
.
If there are errors you want to return to the client, you can redirect the user to redirect_uri
with an error
query parameter. You can optionally include error_description
and error_uri
as query parameters to pass along more information.
Name: intranet
Login Url: https://intranet.company.com/login
We provide a generic OAuth2 implementation that will enable your app to authenicate and communicate with any third-party platforms that supports an OAuth2 API.
Name [required]
String name for the auth configuration. Must only contain alphanumeric characters, dashes, and underscores. Must be unique for each auth each app.
Token URL [required]
URL for the Authorization endpoint for the third-part platform.
Redirect URL [generated]
URL for the Redirection endpoint. This url is generated using the Name
field and will only show up once the auth config is complete. You should copy this url and save it in your client app configuration on the third-party-platform.
Client ID [required]
String client id for your client app on the third-party platform.
Client Secret [required]
String client secret for your client app on the third-party platform.
Scope [optional]
String access token scope for your client app on the third-party platform. It's passed as a query parameter to the authorization url during the auth flow.
Proxy API Domains [optional]
Space delimited list of URL patterns. For platforms that don't support client side API calls, we provide a server side proxy that will relay your api requests and sign them with the correct headers. This list controls what api endpoints your app is allowed to communicate with.
All url patterns must be valid http or https urls.
Refresh Token Strategy [optional, default=NONE]
Platforms tend to differ when it comes to how to refresh an access token with a refresh token. We've implemented the standard strategy as outlined in the OAuth2 spec.
POST
request to the Token Url to refresh an access token with the following parameters: {
"client_id": "...",
"client_secret": "...",
"refresh_token": "...",
"grant_type": "refresh_token",
}
Name:
"gdrive"
Authorization URL:
"https://accounts.google.com/o/oauth2/v2/auth"
Token URL:
"https://www.googleapis.com/oauth2/v4/token"
Client ID:
"..."
Client Secret:
"..."
Scope:
"https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/drive.metadata.readonly"
Proxy API Domains:
"https://*.googleapis.com"
Once auth configurations are completed, you can use the apis below to trigger auth flows or make api requests.
@param {string} authName
String name
of the auth configuration.
@return {quip.apps.Auth}
Returns the auth instance if there's one corresponding to the given name and undefined
otherwise. Depending on the auth config's type, the returned instance may be a quip.apps.UrlAuth
or quip.apps.OAuth2
var auth = quip.apps.auth("gdrive");
This is the object quip.apps.auth
will return if the auth config has the type URL.
Triggers the login flow, which happens in a pop up window on web and in overlay windows on native apps. The window will close automatically if at the end of login flow.
@param {Object=} params [optional]
Object containing custom query parameters to be sent to the login url during the login flow.
@return {Promise}
Returns a promise that resolves if the login is successful and fails if there's an error.
quip.apps.auth("intranet")
.login({query: "param"})
.then(() => console.log("Logged in"))
.catch(error => console.log("Login failed", error));
Once the user is successfully logged in, a cookie should be set on the domain of the login url. To make a Cross Origin AJAX request using the cookie credentials, you can use fetch.
fetch("https://intranet.company.com/api", {
mode: "cors",
credentials: "include"
}).then(response => response.json());
This is the object quip.apps.auth
will return if the auth config has the type OAUTH2.
Triggers the login flow, which happens in a pop up window on web and in overlay windows on native apps. The window will close automatically if at the end of login flow.
@param {Object=} params [optional]
Object containing custom query parameters to be sent to the authorization url during the login flow.
@return {Promise}
Returns a promise that resolves if the login is successful and fails if there's an error.
quip.apps.auth("gdrive")
.login({
"access_type": "offline",
"prompt": "consent",
"scope": "https://www.googleapis.com/auth/drive.readonly",
})
.then(() => console.log("Logged in"))
.catch(error => console.log("Login failed", error));
@return {Boolean}
Boolean value that represents whether the user is logged in.
quip.apps.auth("gdrive").isLoggedIn()
// returns True
@return {Object}
Returns the token response returned by the OAuth2 token endpoint during the login flow.
quip.apps.auth("gdrive").getTokenResponse()
// returns {
// access_token: "...",
// expires: 3600,
// token: "Bearer"
// }
@param {string} paramName [required]
String name of the field you want to query.
@return {*}
Returns the field in corresponding to the paramName
in the token response.
quip.apps.auth("gdrive").getTokenResponseParam("access_token")
// returns "ya29..."
@return {Promise<http.Response>}
Returns a promise that resolves to an http.Response
object containing the result of the server's attempt to refresh the user's access token. It's required that Refresh Token Strategy is set to a value other than NONE.
quip.apps.auth("gdrive").refreshToken()
.then(response => console.log(
response.status,
response.json()))
@return {Promise<http.Response>}
Returns a promise that resolves to an http.Response
object containing the result of the server's attempt to log out.
quip.apps.auth("gdrive").logout()
.then(response => console.log(
response.status,
response.json()))
Apps that have configured Proxy API Domains can use this method to make api calls to third-party platforms.
@param {RequestParams} params [required]
Returns a promise that resolves to an http.Response
object containing the result of the server's attempt to log out.
url: string [required]
String url that matches at least one of the Proxy API Domains.
method: string [optional, default="GET"]
String http method. Can be one of GET, HEAD, POST, PUT, PATCH, DELETE.
query: !Object<string, *> [optional]
Object containing query parameters to be appended to the url
headers: !Object<string, *> [optional]
Object containing custom http headers to be sent with the request.
data: !Object<string, *> [optional]
Object containing data to be sent in the body of the request.
@return {Promise<http.Response>}
Returns a promise that resolves to an http.Response
object containing the result of the request.
quip.apps.auth("gdrive").request({
url: "https://www.googleapis.com/drive/files/root"
}).then(response => response.json());
manifest_version
Integer specifying the version of the manifest file format required by this app package. Should always be 1.
name
String name for the app type.
version_name
Dot-separated string representing the version of this app implementation; may be exposed to users.
version_number
Integer version number representing the version of this app implementation. This value must increase each time a new version of the app is uploaded in the developer portal.
sizing_mode
Choose one of three options to specify how the app will be sized. The term "container" used below refers to the app’s container in the Quip document, which will generally occupy the full document width unless the app is in a side-by-side layout.fill_container
The app’s width is set to its container’s width and its height is determined by its contents. If "fill_container_max_width" is specified, the app’s width is set to that value when its container’s width is larger.scale
The app’s width is set to its "initial_width" and its height is determined by its contents. It is expected to always maintain a constant width/height aspect ratio and automatically scales down in smaller containers. It can also optionally be manually resizable.fit_content
The app’s width and height are both determined by its contents and are independent of its container’s width. If the app overflows past its container’s width, it will become horizontally scrollable.
initial_height
Integer height in pixels for a newly created, empty instance of this app. This value is only used to determine the height of the loading placeholder when the app is created.
initial_width
Integer width in pixels for a newly created, empty instance of this app. This only applies to app with "scale" or "fit_content" sizing. For apps with "fit_content" sizing, this value is only used to determine the width of the loading placeholder when the app is created.
fill_container_max_width
The maximum width of the app. This only applies to apps with "fill_container" sizing.
js_files
The list of JS files to be injected into the app frame. These are injected in the order they appear in this array. The paths specified here are relative to the root of the package.
css_files
The list of CSS files to be injected into the app frame. These are injected in the order they appear in this array. The paths specified here are relative to the root of the package.
other_resources
A list of relative file path patterns. Files in the package matching these patterns will be uploaded to Quip and will be available as resources to the app. These could include image or font resource files, for example.
csp_sources
Map of CSP directives that should be included in the app. The keys are any of "connect_srcs", "font_srcs", "frame_srcs", "img_srcs", "media_srcs", "script_srcs" or "style_srcs", and the values are lists of domains/keywords that should be allowed for that CSP directive. For example, "script_srcs": ["https://foo.com", "https://bar.com"] will generate the CSP header value "script-src https://foo.com https://bar.com;".
Specifying these will allow JS/CSS/images/fonts/etc to be fetched from remote servers, allow the app to make external AJAX requests, and/or allow the app to embed iframes of its own. However, they may cause the app to not work correctly in an offline native client. The domains must use https or be a known keyword, e.g. "'self'", "'none'".
intercept_url_patterns
List of URL patterns that the editor should intercept when pasting a link and create an instance of this app instead. See for how this data is passed to the app. These URL patterns may include * to represent a wildcard and are otherwise treated as prefixes (only a prefix match is necessary in order to match with a pasted URL). These URL patterns must use a protocol of either http or https. Note that these will only work for apps that are globally available to Quip users.
toolbar_color
String representing the color that the main toolbar menu button should be colored. Should be one of ["red", "orange", "yellow", "green", "blue", "violet"].
allow_tiny_size
Set this to true if the app’s width and/or height will ever need to be less than 50px.
disable_app_level_comments
Setting this to true will prevent commenting on the app as a whole (the comment bubble normally in the margin of the Quip document will not be shown)
{
"id": "cSVfwAKVsWF",
"manifest_version": 1,
"name": "My Cool App",
"version_name": "1.0",
"version_number": 1,
"sizing": "fit_content",
"initial_height": 300,
"initial_width": 800,
"disable_app_level_comments": true,
"js_files": [
"dist/app.js"
],
"css_files": [
"dist/app.css"
]
}
Ask our team and the Quip developer community over at the Salesforce Stack Exchange.